文件夹
javascript与node.js
    javascript与你
    因为javascript真正意义上有两种,甚至能够说是三种形态(从最早的作为DHTML进行增强的小工具,到像jQuery那样严格意义上的前端技术。再到如今的服务端技术),因此,比較难找到一个”正确“的方式来学习JavaScript,使得让你书写Node.js应用的时候感觉自己是在真正开发它而不不过使用它。
    简短声明
    这是一个”从0基础入门到高级入门“的介绍资料。

    server端javascript
    javascript最早是执行在浏览器中。然而浏览器仅仅是提供了一个上下文,它定义了使用javascript能够做什么。但并没有”说“太多关于JavaScript语言本身能够做什么。其实,javascript是一门”完整“的语言:它能够使用在不同的上下文中。其能力与其他同类语言相比有过之而无不及。
    Node.js其实就是第二种上下文。它同意在后端(脱离浏览器环境)执行JavaScript。
    要实如今后台运行javascript代码。代码须要先被解释然后正确的运行。

Node.js的原理正是如此,它使用了Google的V8虚拟机(Google的Chrome浏览器使用的JavaScript运行环境)。来解释和运行JavaScript代码。

    除此之外,伴随着Node.js的还有很多实用的模块,它们能够简化非常多反复的劳作,比方向终端输出字段串。

    因此,Node.js其实既是一个执行时环境。同一时候又是一个库。

    要使用Node.js,首先须要进行安装。
    "hello world"
    创建一个helloworld.js文件。
    写入console.log("Hello World");
    保存该文件,通过Node.js来运行
    node helloworld.js
    终端输出Hello World

一个完整的基于Node.js的web应用
    用例
    我们来把目标设定简单点,只是也要够实际才行:
    用户能够通过浏览器使用我们的应用。

    当用户请求http://domain/start时,能够看到一个欢迎页面,页面上有一个文件上传的表单。
    用户能够选择一个图片并提交表单,随后文件将被上传到http://domain/upload。该页面完毕上传后会把图片显示在页面上。

    更进一步地说,在完毕这一目标的过程中。我们不只须要基础的代码而无论代码是否优雅。

我们还要对此进行抽象,来寻找一种适合构建更为复杂的Node.js应用的方式。

    应用不同模块分析
    我们来分解一下这个应用,为了实现上文的用例。我们须要实现哪些部分呢?
    我们须要提供Web页面,因此须要一个HTTPserver
    对于不同的请求,依据请求的URL。我们的server须要给予不同的响应,因此我们须要一个路由,用户把请求相应到请求处理程序(request handler)
    路由还应该能处理POST数据。而且把数据封装成更友好的格式传递给请求处理入程序。因此须要请求数据处理功能
    我们不只要处理URL相应的请求。还要把内容显示出来。这意味着我们须要一些视图逻辑供请求处理程序使用。以便将内容发送给用户的浏览器
    最后。用户须要上传图片,所以我们须要上传处理功能来处理这方面的细节
我们先来想想,使用PHP的话我们会怎么构建这个结构。一般来说我们会用一个Apache HTTPserver并配上mod_php5模块。
从这个角度看。整个“接收HTTP请求并提供Web页面”的需求根本不须要PHP来处理。

只是对Node.js来说,概念全然不一样了。使用Node.js时,我们不只在实现一个应用。同一时候还实现了整个HTTPserver。其实,我们的Web应用以及相应的Webserver基本上是一样的。

如今開始从HTTPserver着手。

构建应用的模块
    一个基础的HTTPserver
    讨论把全部东西放进一个文件中吗?为了保持代码的可读性。把不同功能的代码放入不同的模块中,保持代码分离。

    这样的方法同意你拥有一个干净的主文件(main file)。你能够用Node.js运行它。同一时候你能够拥有干净的模块,它们能够被主文件和其他的模块调用。
    那么。如今我们来创建一个用于启动我们的应用的主文件,和一个保持着我们HTTPserver代码的模块。
    把主文件叫做index.js或多或少是个标准格式。

把服务器模块放进叫server.js的文件中则非常好理解。

    让我们先从服务器模块開始。在你的项目的根文件夹下创建一个叫server.js的文件。并写入下面代码:
    var http = require("http");
    http.createServer(function(request, response){
        response.writeHead(200, {"Content-Type": "text/plain"});
        response.write("Hello World");
        response.end();
    }).listen(8888);
    搞定!你刚刚完毕了一个能够工作的HTTPserver。为了证明这一点,我们来执行而且測试这段代码。

首先。用Node.js执行你的脚本:

    node server.js
    接下来,打开浏览器訪问http://localhost:8888/,你会看到一个写着"Hello World"的网页。
    这非常有趣。不是吗?让我们先来谈谈HTTPserver的问题,把怎样组织项目的事情先放一边吧。你认为怎样?我保证之后我们会解决那个问题的。
    分析HTTPserver
    那么接下来,让我们分析一下这个HTTPserver的构成。
    第一行请求(require)Node.js自带的http模块。而且把它赋值给http变量。
    接下来我们调用http模块提供的函数:createServer。这个函数会返回一个对象,这个对象有一个叫做listen的方法,这种方法有一个数值參数,指定这个HTTPserver监听的port号。
    咱们临时先无论http.createServer的括号中的那个函数定义。

    我们本来能够用这种代码来启动server并侦听8888port:
    var http = require("http");
    var server = http.createServer();
    server.listen(8888);
    这段代码仅仅会启动一个侦听8888port的server。它不做不论什么别的事情。甚至连请求都不会应答。
    最有趣(并且,假设你之前习惯使用一个更加保守的语言。比方PHP,它还非常奇怪)的部分是createServer()的第一个參数,一个函数定义。

    实际上,这个函数定义是createServer()的第一个也是唯一一个參数。

由于在JavaScript中,函数和其它变量一样都是能够被传递的。

    进行函数传递
    举例来说,你能够这样做:
    function say(word){
        console.log(word);
    }
    function execute(someFunction, value){
        someFunction(value);
    }
    execute(say, "Hello");
    请细致阅读这段代码!在这里。我们把say函数作为execute函数的第一个变量进行了传递。

这里返回的不是say的返回值。而是say本身!

    这样一来,say就变成了execute中的本地变量someFunction,execute能够通过调用someFunction()(带括号的形式)来使用say函数。
    当然,由于say有一个变量,execute在调用someFunction时能够传递这样一个变量。

    我们能够,就像刚才那样,用它的名字把一个函数作为变量传递。

可是我们不一定要绕这个“先定义,再传递”的圈子,我们能够直接在还有一个函数的括号里定义和传递这个函数:

    function execute(someFunction, value){
        someFunction(value);
    }
    execute(function(word){
        console.log(word);
    }, "Hello");
    我们在ececute接受第一个參数的地方直接定义了我们准备传递给execute的函数。
    用这样的方式,我们甚至不用给这个函数起名字,这也是为什么它被叫做匿名函数。
    这是我们和我所觉得的“进阶”JavaScript的第一次亲热接触,只是我们还是得循序渐进。如今。我们先接受这一点:在JavaScript中,一个函数能够作为还有一个函数接收一个參数。

我们能够先定义一个函数。然后传递,也能够在传递參数的地方直接定义函数。

    函数传递是怎样让HTTPserver工作的
带着这些知识。我们再来看看我们简约而不简单的HTTPserver:
    var http = require("http");
    http.createServer(function(){
        response.writeHead(200, {"Content-Type": "text/plain"});
        response.write("Hello World");
        response.end();
    }).listen(8888);
    如今它看上去应该清晰了非常多:我们想createServer函数传递了一个匿名函数。

    用这种代码也能够达到相同的目的:
    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);
    或许如今我们该问这个问题了:我们为什么要用这样的方式呢?

    基于事件驱动的回调
这个问题可不好回答(至少对我来说),只是这是Node.js原生的工作方式。它是事件驱动的,这也是它为什么这么快的原因。

这一切都归结于"Node.js是事件驱动的"这一事实。好吧,事实上我也不是特别确切的了解这句话的意思。只是我会试着解释,为什么它对我们用Node.js写网络应用(Web based application)是有意义的。
当我们使用http.createServer方法的时候,我们当然不仅仅是想要一个侦听某个port的server,我们还想要它在server收到一个HTTP请求的时候做点什么。
问题是,这是异步的:请求不论什么时候都可能到达,可是我们的server却跑在一个单进程中。

写PHP应用的时候,我们一点也不为此操心:不论什么时候当有请求进入的时候,网页server(一般是Apache)就为这一请求新建一个进程,而且開始从头到尾运行对应的PHP脚本。
那么在我们的Node.js程序中。当一个新的请求到达8888port的时候,我们怎么控制流程呢?
嗯,这就是Node.js/JavaScript的事件驱动设计可以真正帮上忙的地方了--尽管我们还得学一些新概念才干掌握它。让我们来看看这些概念是怎么应用在我们的server代码里的。
我们创建了server,而且向创建它的方法传递了一个函数。

不管何时我们的server收到一个请求,这个函数就会被调用。

我们不知道这件事情什么时候会发生,可是我们如今有了一个处理请求的地方:它就是我们传递过去的那个函数。至于它是被预先定义的函数还是匿名函数,就无关紧要了。
这个就是传说中的回调。我们给某个方法传递了一个函数。这种方法在有对应事件发生时调用这个函数来进行回调。
至少对我来说。须要一些功夫才干弄懂它。

让我们再来琢磨琢磨这个新概念。我们怎么证明。在创建完server之后。即使没有HTTP请求进来、我们的回调函数也没有被调用的情况下,我们的代码还继续有效吗?我们试试这个:
    var http = require("http");
    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.");
    注意:在onRequest(我们的回调函数)触发的地方。我用console.log输出了一段文本。在HTTPserver開始工作之后,也输出一段文本。

当我们与往常一样。执行它node server.js时,它会立即在命令行上输出"Server has started."。

当我们向服务器发出请求(在浏览器訪问http://localhost:8888/),"Request received."这条消息就会在命令行中出现。

这就是事件驱动的异步server端JavaScript和它的回调啦!
(请注意,当我们在server訪问网页时。我们的server可能会输出两次"Request received."。那是由于大部分server都会在你訪问http://localhost:8888/时尝试读取http://localhost:8888/favicon.ico)

    server是怎样处理请求的
好的,接下来我们简单分析一下我们server代码中剩下的部分,也就是我们的回调函数onRequest()的主体部分。
当回调启动。我们的onRequest()函数被触发的时候,有两个參数被传入:
request和response。

它们是对象。你能够使用它们的方法来处理HTTP请求的细节,而且响应请求(比方向发出请求的浏览器发回一些东西)。
所以我们的代码就是:当收到请求时,使用response.writeHead()函数发送一个HTTP状态200和HTTP头的内容类型(content-type),使用response.write()函数在HTTP对应主体中发送文本“Hello World”。

最后,我们调用response.end()完毕响应。
眼下来说,我们对请求的细节并不在意。所以我们没有使用request对象。

    服务端的模块放在哪里
我们如今在server.js文件里有一个很基础的HTTP服务器代码,并且我提到通常我们会有一个叫index.js的文件去调用应用的其它模块(比方server.js中的HTTP服务器模块)来引导和启动应用。

如今看看怎么把server.js变成一个真正的Node.js模块,使它能够被我们(还没动工)的index.js主文件使用。

我们已经在代码中使用了模块。

像这样:

var http = require("http");
...
http.createServer(...);
Node.js中自带了一个叫做"http"的模块,我们在我们的代码中请求它并把返回值赋给一个本地变量。

这把我们的本地变量变成了一个拥有全部http模块所提供的公共方法的对象。
给这样的本地变量起一个和模块名称一样的名字是一种惯例,可是你也能够依照自己的喜好来:
其实,我们不用做太多的改动。把某段代码变成模块意味着我们须要把我们希望提供其功能的部分导出到请求这个模块的脚本。

眼下,我们的HTTPserver须要导出的功能很easy,由于请求server模块的脚本不过须要启动server而已。
我们把我们的server脚本放到一个叫做start的函数里。然后我们会导出这个函数。

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中。
创建index.js文件并写入下面内容:
var server = require("./server");
server.start();
正如你所示,我们能够像使用不论什么其他的内置模块一样使用server模块:请求这个文件并把它指向一个变量。当中已导出的函数就能够被我们使用了。
我们如今就能够从我们的主要脚本启动我们的应用。而它还是老样子:
node index.js
     我们如今能够把我们的应用的不同部分放入不同的文件中,而且通过生成模块的方式把它们连接在一起了。

     我们仍然仅仅拥有整个应用的最初部分:我们能够接收HTTP请求。

可是我们得做点什么--对于不同的URL请求,server应该有不同的反应。

     对于一个很easy的应用来说,你能够直接在回调函数onRequest()中做这件事情。只是就像我说过的,我们应该增加一些抽象的元素,让我们的样例变得更有趣一点儿。
     处理不同的HTTP请求在我们的代码中是一个不同的部分,叫做“路由选择”--那么,我们接下来就创造一个叫做路由的模块。


    怎样来进行请求的"路由"
     我们要为路由提供请求的URL和其他须要的GET及POST參数。随后路由须要依据这些数据来运行相应的代码(这里“代码”相应整个应用的第三部分:一系列在接收到请求时真正工作的处理程序)。

     我们须要的全部数据都会包括在request对象中,该对象作为onRequest()回调函数的第一个參数传递。可是为了解析这些数据。我们须要额外的Node.js模块。它们各自是url和querystring模块。

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvenFqZmxhc2g=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" height="322" width="412" alt="" style="">


    当然我们也能够用querystring模块来解析POST请求体中的參数,稍后会有演示。
     如今我们来给onRequest()函数加上一些逻辑。用来找出浏览器请求的URL路径:
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);
    console.log("Server has started.");
}
exports.start = start;
     我们的应用如今能够通过请求的URL路径来差别不同请求了-这使我们得以使用路由(还未完毕)来将请求以URL路径为基准映射到处理程序上。
     在我们所要构建的应用中,这意味着来自/start和/upload的请求能够使用不同的代码来处理。稍后我们将看到这些内容是怎样整合到一起的。
     如今我们能够来编写路由,建立一个名为router.js的文件,加入下面内容:
function route(pathname){
    console.log("About to route a request for " + pathname);
}
exports.route = route;
     这段代码貌似什么也没干,只是对于如今来说这是应该的。

在加入很多其它的逻辑曾经,我们先来看看怎样把路由和server整合起来。

     我们的server应当知道路由的存在并加以有效利用。

当然能够通过硬编码的方式将这一依赖绑定到server上,可是其他语言的编程经验告诉我们这会是一件很痛苦的事,因此我们将使用依赖注入的方式较松散地加入路由模块。

     首先,我们来扩展一下server的start()函数,以便将路由函数作为參数传递过去:
var http = require("http");
var url = require("url");
function start(route){
    function onRequest(request, response){
        var pathname = url.parse(request.url).pathname;
        console.log("Request for " + pathname + " received.");
        route(pathname);
        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,使得路由函数能够被注入到server中:
var server = require("./server");
var router = require("./router");
server.start(router.route);
在这里,我们传递的函数依然什么也没做。
假设如今启动应用(node index.js。始终记得这个命令行)。随后请求一个URL,你将会看到应用输出对应的信息。这表明我们的HTTPserver已经在使用路由模块了。并会将请求的路径传递给路由:
node index.js
Request for /foo received.
About to route a request for /foo
以上输出已经去掉了favicon.icon请求相关部分。

    行为驱动运行
在index文件里,我们能够将router对象传递进去。server随后能够调用这个对象的route函数。
就像这样,我们传递一个东西,然后server利用这个东西来完毕一些事。

可是server事实上不须要这种东西。

它仅仅须要把事情做完即可,事实上为了把事情做完,你根本不须要东西,你须要的是动作。


    路由给真正的请求处理程序
     路由,是指我们要针对不同的URL有不同的处理方式。

比如处理/start的“业务逻辑”就应该和处理/upload的不同。

     在如今的实现下,路由过程会在路由模块中“结束”,而且路由模块并非真正针对请求“採取行动”的模块,否则当我们的应用程序变得更为复杂时。将无法非常好地扩展。
     我们临时把作为路由目标的函数称为请求处理程序。

如今我们不要急着来开发路由模块,由于假设请求处理没有就绪的话,再怎么完好路由模块也没有多大意义。

     应用程序须要新的部件,我们来创建一个叫做requestHandlers的模块。并对于每个请求处理程序。加入一个占位用函数。随后将这些函数作为模块的方法导出:
     function start(){
          console.log("Request handler 'start' was called.");
     }
     function upload(){
          console.log("Request handler 'upload' was called.");
     }
     exports.start = start;
     exports.upload = upload;
这样我们就能够把请求处理程序和路由模块连接起来。让路由“有路可寻”。
在这里我们得做个决定:是将requestHandlers模块硬编码到路由里来使用。还是再加入一点依赖注入?尽管和其他模式一样,依赖注入不应该只为使用而使用,但在如今这个情况下,使用依赖注入能够让路由和请求处理程序之间的耦合更加松散,也因此能让路由的重用性更高。
这意味着我们得将请求处理程序从server传递到路由中,但感觉上这么做更离谱了。我们得一路把这堆请求处理程序从我们的主文件传递到server中。再将之从server传递到路由。
那么我们要怎么传递这些请求处理程序呢?别看如今我们仅仅有2个处理程序,在一个真实的应用中。请求处理程序的数量会不断添加。我们当然不想每次有一个新的URL或请求处理程序时,要为了在路由里完毕请求到处理程序的映射而重复折腾。除此之外。在路由里有一大堆 if request == x then call handle y也使得系统丑陋不堪。

细致想想,有一大堆东西,每一个都要映射到一个字符串(就是请求的URL)上?似乎关联数组(associative array)能完美胜任。
只是结果有点令人失望,JavaScript没提供关联数组--也能够说它提供了?其实,在JavaScript总,真正能提供此类功能的是它的对象。
在这方面。msdn提供了一段摘要:
在C++或C#中,当我们谈到对象,指的是类或者结构体的演示样例。

对象依据他们实例化的模板(就是所谓的类)。会拥有不同的属性的和方法。但在JavaScript里对象不是这个概念。

在JavaScript中,对象就是一个键值对的集合,你能够把JavaScript的对象想象成一个键为字符串类型的字典。

但假设JavaScript的对象不过键值对的集合,它又怎么会拥有方法呢?
如今我们已经确定将一系列请求处理程序通过一个对象来传递,而且须要松耦合的方式将这个对象注入到route()函数中。
我们先将这个对象引入到主文件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;
server.start(router.route, handle);
尽管handle并不不过一个”东西“(一些请求处理程序的集合),我还是建议以一个动词作为其命名。这样做能够让我们在路由中使用更流畅的表达式,稍后会有说明。
正如所见。将不同的URL映射到同样的请求处理程序上是非常easy的:仅仅要在对象中加入一个键为"/"的属性。相应requestHandlers.start就可以。这样我们就能够干净简洁地配置/start和/的请求都交由start这一处理程序处理。
在完毕了对象的定义后。我们把它作为额外的參数传递给服务器,为此将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.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;

这样我们就在start()函数里加入了handle參数,而且把handle对象作为第一个參数传递给了route()回调函数。
然后我们对应地在route.js文件里改动route()函数:
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;
通过以上代码。我们首先检查给定的路径相应的请求处理程序是否存在,假设存在的话直接调用相应的函数。

我们能够用从关联数组中获取元素一样的方式从传递的对象中获取请求处理函数,因此就有了简洁流畅的形如handle[pathname]();的表达式。

有了这些,我们就把server、路由和请求处理程序在一起了。

如今我们启动应用程序并在浏览器中訪问http://localhost:8888/start,下面日志能够说明系统调用了正确的请求处理程序:

Server has started.
Request for /start received.
About to route a request for /start
Request handler 'start' was called.
而且在浏览器中打开http://localhost:8888能够看到这个请求相同被start请求处理程序处理了:
Request for / received.
Aboute to route a request for /
Request handler 'start' was called.

让请求处理程序作出响应
事实上处理请求就是对请求做出响应。因此,我们须要让请求处理程序可以像onRequest函数那样可以和浏览器进行"对话"。
        
不好的实现方式
对于我们这样拥有PHP或者Ruby技术背景的开发人员来说,最直截了当的实现方式其实并非很靠谱:看似有效,实则未必如此。        

这里按直截了当的实现方式,让请求处理程序通过onRequest函数直接返回(return())他们要展示给用户的信息。

让我们从让请求处理程序返回须要在浏览器中显示的信息開始。

我们须要将requestHandler.js改动为例如以下形式:

function start() {
     console.log("Request handler 'start' was called.");
     return "Hello Start";
}
function upload() {
     console.log("Request handler 'upload' was called.");
     return "Hello Upload";
}
exports.start = start;
exports.upload = upload;
好的。相同的,请求路由须要将请求处理程序返回给它的信息返回给server。因此,我们须要将router.js改动为例如以下形式:
function route(handle, pathname) {
     console.log("About to route a request for " + pathname);
     if (typeof handle[pathname] === 'function') {
          return handle[pathname]();
     } else {
          console.log("No request handler found for " + pathname);
          return "404 Not found";
     }
}
exports.route = route;
正如上述代码所看到的,当请求无法路由的时候。我们也返回了一些相关的错误信息。

最后。我们须要对我们的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.");
          response.writeHead(200, {"Content-Type": "text/plain"});
          var content = route(handle, pathname);
          response.write(content);
          response.end();
     }
     http.createServer(onRequest).listen(8888);
     console.log("Server has started.");
}
exports.start = start;
假设我们执行重构后的应用。一切都会工作非常好
http://localhost:8888/foo 会输出404 Not Found
当有请求处理程序须要进行非堵塞的操作时候,我们的应用就"挂"了。

堵塞与非堵塞
正如此前所提到的,当在请求处理程序中包含非堵塞操作时就会出问题。

可是,在说这之前。我们先看看什么是堵塞操作。

这里,我们来改动下start请求处理程序,我们让它等待10秒以后再返回"Hello Start"。由于,JavaScript中没有类似sleep()这种操作,所以这里仅仅可以来点小Hack来模拟实现。

让我们将requestHandlers.js改动成例如以下形式:
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";
}
function upload() {
     console.log("Request handler 'upload' was called.");
     return "Hello Upload";
}
exports.start = start;
exports.upload = upload;
上述代码中。当函数start()被调用时,Node.js会先等待10秒。之后才会返回"hello Start"。当调用upload()的时候,会和此前一样马上返回。
(当然了,这里仅仅是模拟休眠10秒,实际场景中,这种堵塞操作有非常多。例如说一些长时间的计算操作等。)
接下来就让我们来看看,我们的修改带来了哪些变化。

     如往常一样,我们先要重新启动下server。为了看到效果,我们要进行一些相对复杂的操作:首先,打开两个浏览器窗体或者标签页。在第一个浏览器窗体的地址栏中输入http://localhost:8888/start,可是先不要打开它!

在第二个浏览器窗体的地址栏中输入http://localhost:8888/upload。相同的,先不要打开它。
接下来,做例如以下操作:在第一个窗体中("/start")按下回车。然后高速切换到第二个窗体中("/upload")按下回车。
注意。发生了什么:/start URL载入花了10秒。这和我们预期的一样。可是。/upload URL竟然也花了10秒,而它在相应的请求处理程序中并没有类似于sleep()这种操作!

这究竟是为什么呢?原因就是start()包括了堵塞操作。

形象的说就是“它堵塞了全部其它的处理工作”。

这显然是个问题,由于Node一向是这样来标榜自己的:“在node中除了代码,全部一切都是并行运行的”。
这句话的意思是说,Node.js能够在不新增额外线程的情况下,依旧能够对任务进行并行处理--Node.js是单线程的
。它通过事件轮询(event loop)来实现并行操作,对此。我们应该要充分利用这一点--尽可能的避免堵塞操作,取而代之,多使用非堵塞操作。
然而。要用非堵塞操作,我们须要使用回调,通过将函数作为參数传递给其他须要花时间做处理的函数(例如说,休眠10秒。或者查询数据数据库。又或者是进行大量的计算)。

对于Node.js来说,它是这样处理的:“probablyExpensiveFunction()”(这里指的就是须要花时间处理的函数),你继续处理你的事情,我(Node.js线程)先不等你了,我继续去处理你后面的代码。请你提供一个callbackFunction(),等你处理完之后我会去调用该回调函数的。
接下来。我们会介绍一种错误的使用非堵塞操作的方式。
和上次一样,我们通过改动我们的应用来暴露问题。
这次我们还是拿start请求处理程序来“开刀”。将起改动成例如以下形式:
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;
}
function upload() {
     console.log("Request handler 'upload' was called.");
     return "Hello Upload";
}
exports.start = start;
exports.upload = upload;
上述代码中,我们引入了一个新的Node.js模块,child_process。

之所以用它,是为了实现一个既简单又使用的非堵塞操作:exec()。

exec()做了什么呢?它从Node.js来运行一个shell命令。

在上述样例中,我们用它来获取当前文件夹下全部的文件("ls -lah"),然后,当/startURL请求的时候将文件信息输出到浏览器中。

上述代码是很直观的:创建了一个新的变量content(初始值为"empty"),运行"ls -lah"命令,将结果赋值给content,最后将content返回。
和往常一样,我们启动server,然后訪问"http://localhost:8888/start"。

之后会加载一个美丽的web页面。其内容为"empty"。怎么回事?
这个时候。你可能大致已经猜到了。exec()在非堵塞这块发挥了奇妙的功效。它事实上是个非常好的东西,有了它,我们能够运行非常耗时的shell操作而无须迫使我们的应用停下来等待该操作。
(假设想要证明这一点,能够将"ls -lah"换成比方"find /")这样更耗时的操作来效果)。
然而。针对浏览器显示的结果来看,我们并不惬意我们的非堵塞操作。对吧?
好,接下来,我们来修正这个问题。在这过程中。让我们先来看看为什么当前的这样的方式不起作用。
问题就在于,为了进行非堵塞工作,exec()使用了回调函数。
在我们的样例中。该回调函数就是作为第二个參数传递给exec()的匿名函数:
function (error, stdout, stderr) {
    content = stdout;
}
如今就到了问题根源所在了:我们的代码是同步运行的。这就意味着在调用exec()之后,Node.js会马上运行return content。在这个时候,content仍然是"empty",由于传递给exec()的回调函数还未运行到--由于exec()的操作是异步的。

我们这里"ls -lah"的操作事实上是非常快的(除非当前文件夹下有上百万个文件)。这也是为什么回调函数也会非常快的运行到--只是,无论怎么说它还是异步的。
为了让效果更加明显,我们想象一个更耗时的命令:"find /",它在我机器上须要运行1分钟左右的时间,然后,虽然在请求处理程序中,我把"ls -lah"换成"find /"。当打开/start URL的时候,依旧可以马上获得HTTP响应--非常明显,当exec()在后台运行的时候,Node.js自身会继续运行后面的代码。

而且我们这里如果传递给exec()的回调函数,仅仅会在"find
/"命令运行完毕之后才会被调用。

那到底我们要怎样才干实现将当前文件夹下的文件列表显示给用户呢?
好,了解了这样的不好的实现方式之后。我们接下来来介绍怎样以正确的方式让请求处理程序对浏览器请求作出响应。

以非堵塞操作进行请求响应
我刚刚提到了这样一个短语--“正确的方式”。

而其实通常“正确的方式”一般都不简单。

只是,用Node.js就有这样一种实现方案:函数传递。以下就让我们来详细看看怎样实现。
到眼下为止,我们的应用已经能够通过应用各层之间传递值的方式(请求处理程序->请求路由->server)将请求处理程序返回的内容(请求处理程序终于要显示给用户的内容)传递给HTTPserver。

如今我们採用例如以下这样的新的实现方式:相对採用将内容传递给server的方式,我们这次採用将server"传递"给内容的方式。

从实践角度来说,就是将response对象(从server的回调函数onRequest()获取)通过请求路由传递给请求处理程序。

随后,处理程序就能够採用该对象上的函数来对请求作出响应。原理就是如此。接下来让我们来一步步实现这样的方案。

先从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);
     }
     http.createServer(onRequest).listen(8888);
     console.log("Server has started.");
}
exports.start = start;
相对此前从route()函数获取返回值的做法,这次我们将response对象作为第三个參数传递给route()函数,而且,我们将onRequest()处理程序中全部有关response的函数回调都移除,由于我们希望这部分工作让route()函数来完毕。

以下就来看看我们的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);
          response.writeHead(404, {"Content-Type": "text/plain"});
          response.write("404 Not found");
          response.end();
     }
}
exports.route = route;
相同的模式:相对此前从请求处理程序中获取返回值,这次取而代之的是直接传递response对象。
假设没有相应的请求处理器处理,我们就直接返回"404"错误。
最后,我们将requestHandler.js改动为例如以下形式:
var exec = require("child_process").exec;
function start(response) {
     console.log("Request handler 'start' was called.");
     exec("ls -lah", function(error, stdout, stderr) {
         response.writeHead(200, {"Content-Type": "text/plain"});
          response.write(stdout);
          response.end();
     });
}
function upload(response) {
     console.log("Request handler 'upload' was called.");
     response.writeHead(200, {"Content-Type": "text/plian"});
     response.write("Hello Upload");
     response.end();
}
exports.start = start;
exports.upload = upload;
我们的处理程序函数须要接收response參数,为了对请求作出直接的响应。
start处理程序在exec()的匿名回调函数中做请求响应的操作。而upload处理程序仍然是简单的回复"Hello World",仅仅是这次是使用response对象而已。

这时再次我们启动应用(node index.js),一切都会工作的非常好。
假设想要证明/start处理程序中耗时的操作不会堵塞对/upload请求做出马上响应的话。能够将requestHandlers.js改动为例如以下形式:
var exec = require("child_process").exec;
function start(response) {
     console.log("Request handler 'start' was called.");
     exec("find /",
          {
               timeout: 10000,
               maxBuffer: 20000*1024
          },
          function(error, stdout, stderr) {
               response.writeHead(200, {"Content-Type": "text/plain"});
               response.write(stdout);
               response.end();
          }
     );
}
function upload(response) {
     console.log("Request handler 'upload' was called.");
     response.writeHead(200, {"Content-Type": "text/plain"});
     response.write("Hello Upload");
     response.end();
}
exports.start = start;
exports.upload = upload;
这样一来。当请求http://localhost:8888/start的时候,会花10秒钟的时间才加载,而当请求http://localhost:8888/upload的矢耦,会马上响应,纵然这个时候/start响应还在处理中。
更实用的场景
     到眼下位置,我们做的已经非常好了,可是,我们的应用没有实际用途。
server,请求路由以及请求处理程序都已经完毕了,以下让我们依照此前的用例给站点加入交互:用户选择一个文件,上传该文件,然后在浏览器中看到上传的文件。

为了保持简单。我们如果用户仅仅会上传图片。然后我们应用将该图片显示到浏览器中。

好。以下就一步步来实现。鉴于此前已经对javascript原理性技术性的内容做过大量介绍了。这次我们加快点速度。
要实现该功能,分为例如以下两步:首先。让我们来看看怎样处理POST请求(非文件上传),之后。我们使用Node.js的一个用于文件上传的外部模块。之所以採用这样的实现方式有两个理由。

第一,虽然在Node.js中处理基础的POST请求相对照较简单,但在这过程中还是能学到非常多。

第二,用Node.js来处理文件上传(multipart POST请求)是比較复杂的,它不在本书的范畴。但。怎样使用外部模块却是在本书涉及内容之内。
处理POST请求
考虑这样一个简单的样例:我们显示一个文本区(textarea)供用户输入内容,然后通过POST请求提交给server。最后,server接受到请求。通过处理程序将输入的内容展示到浏览器中    
/start请求处理程序用于生成带文本区的表单。因此,我们将requestHandlers.js改动为例如以下形式:
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\' method=\'post\'>',
               '<textarea name=\'text\' rows=\'20\' cols=\'60\'></textarea>',
               '<input type=\'submit\' value=\'Submit text\' />',
          '</form>',
               '</body>',
          '</html>'
     ].join("");
     response.writeHead(200, {"Content-Type": "text/html"});
     response.write(body);
     response.end();
}
function upload(response) {
     console.log("Request handler 'upload' was called.");
     response.writeHead(200, {"Content-Type": "text/plain"});
     response.write("Hello Upload");
     response.end();
}
exports.start = start;
exports.upload = upload;
如今能够通过http://localhost:8888/start就能够看到简单的表单,须要重新启动server。
接下探讨一个问题:当用户提交表单时,触发/upload请求处理程序处理POST请求的问题。
如今,採用异步回调来实现非堵塞地处理POST请求的数据。
这里採用非堵塞方式处理是明智的,由于POST请求一般都比較重--用户可能会输入大量的内容。

用堵塞的方式处理大数据量的请求必定会导致用户操作的堵塞。

为了使整个过程非堵塞,Node.js会将POST数据拆分成非常多小的数据块。然后通过触发特定的事件。将这些小数据块传递给回调函数。这里的特定的事件有data事件(表示新的小数据块到到达了)以及end事件(表示全部的数据都已经接收完成)。
我们须要告诉Node.js当这些事件触发的时候,回调哪些函数。我们通过在request对象上注冊监听器(listener)来实现。这里的request对象是每次接收到HTTP请求时候。都会把该对象传递给onRequest回调函数。
例如以下所看到的:
request.addListener("data", function(chunk) {
     //called when a new chunk of data was received
});
request.addListener("end", function() {
     //called when all chunks of data have been received
});
问题来了。这部分逻辑写在哪里?我们如今仅仅是在server中获取到了request对象--我们并没有像之前response对象那样。把request对象传递给请求路由和请求处理程序。
在我看来,获取全部来自请求的数据,然后将这些数据给应用层处理,应该是HTTPserver要做的事情。

因此,我建议,我们直接在server中处理POST数据,然后将终于的数据传递给请求路由和请求处理器,让他们来进行进一步的处理。

因此,实现思路就是,将data和end事件的回调函数直接放在server中,在data事件回调中收集全部的POST数据,当接收到全部数据,触发end事件后。其回调函数调用请求路由。并将数据传递给它。然后。请求路由再将该数据传递给请求处理程序。
先从server.js開始:
var http = require("http");
var url = require("url");
function start(route, handle) {
     function onRequest(request, response) {
          var postData = "";
          var pathname = url.parse(request.url).pathname;
          console.log("Request for " + pathname + " received.");
          request.setEncoding("utf8");
          request.addListener("data", function(postDataChunk) {
               postData += postDataChunk;
               console.log("Received POST data chunk '" + postDataChunk + "'.");
          });
          request.addListener("end", function() {
               route(handle, pathname, response, postData);
          });
     }
     http.createServer(onRequest).listen(8888);
     console.log("Server has started.");
     exports.start = start;
}
上述代码做了三件事情:首先。我们设置了接收数据的编码格式为utf-8。然后注冊了"data"事件的监听器,用于收集每次接收到的新数据块,并将其赋值给postData变量,最后,我们将请求路由的调用移动end事件处理程序中,以确保它仅仅会当全部数据接收完成后才触发。而且仅仅触发一次。我们同一时候还把POST数据传递给请求路由,由于这些数据,请求处理程序会用到。
上述代码在每一个数据块到达的时候输出了日志。这对于终于环境来说,是非常不好的(数据量可能会非常大)。可是。在开发阶段是非常实用的。有助于让我们看到发生了什么。
我建议能够尝试下,尝试着去输入一小段文本,以及大段内容,当大段内容的时候,就会发现data事件会触发多次。
接下来在/upload页面,展示用户输入的内容。要实现该功能,我们须要将postData传递给请求处理程序,改动router.js为例如以下形式:
function route(handle, pathname, response, postData) {
     console.log("About to route a request for " + pathname);
     if (typeof handle[pathname] === 'function') {
          handle[pathname](response, postData);
     } 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中,我们将数据包括在对upload请求的响应中:
function start(response, postData) {

     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\' method=\'post\'>',

               '<textarea name=\'text\' rows=\'20\' cols=\'60\'></textarea>',

               '<input type=\'submit\' value=\'Submit text\' />',

          '</form>',

               '</body>',

          '</html>'

     ].join("");

     response.writeHead(200, {"Content-Type": "text/html"});

     response.write(body);

     response.end();

}

function upload(response, postData) {

     console.log("Request handler 'upload' was called.");

     response.writeHead(200, {"Content-Type": "text/plain"});

     response.write("You've sent: " + postData);

     response.end();

}

exports.start = start;

exports.upload = upload;
我们如今能够接收POST数据并在请求处理程序中处理该数据。
最后,当前我们是把请求的整个消息体传递给了请求路由和请求处理程序。我们应该仅仅把POST数据中。我们感兴趣的部分传递给请求路由和请求处理程序。在我们这个样例中,我们感兴趣的事实上仅仅是text字段。

我们能够使用此前介绍过querystring模块来实现requestHandlers.js:
var querystring = require("querystring");

function start(response, postData) {

     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\' method=\'post\'>',

               '<textarea name=\'text\' rows=\'20\' cols=\'60\'></textarea>',

               '<input type=\'submit\' value=\'Submit text\' />',

          '</form>',

               '</body>',

          '</html>'

     ].join("");

     response.writeHead(200, {"Content-Type": "text/html"});

     response.write(body);

     response.end();

}

function upload(response, postData) {

     console.log("Request handler 'upload' was called.");

     response.writeHead(200, {"Content-Type": "text/plain"});

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

     response.end();

}

exports.start = start;

exports.upload = upload;
以上就是关于处理POST数据的所有内容。

处理文件上传
最后,我们来实现终于的用例:同意用户上传图片,并将该图片在浏览器中显示出来。
回到90年代,这个用例全然能够满足用于IPO的商业模型。现在。通过它能够接到两个内容:怎样安装外部Node.js模块。以及怎样将它们应用到我们应用中。
这里我们要用到的外部模块是Felix Geisendorfer开发的node-formidable模块。它对解析上传的文件数据做了非常好的抽象。

处理文件上传"就是"处理POST数据--可是,麻烦的是在详细的处理细节,所以,这里採用现成的方案更合适点。

使用该模块。首先须要安装该模块。

Node.js有它自己的包管理器。叫NPM。它能够让安装Node.js的外部模块变得很方便。通过例如以下一条命令就能够完毕该模块的安装:

npm install formidable
假设安装出现错误。运行
npm config set registry http://registry.cnpmjs.org
再又一次npm install formidable
假设终端输出例如以下内容:
npm info build Success: formidable@1.0.9
npm ok
说明模块已经成功安装了。

如今我们就能够用formidable模块了--使用外部模块与内部模块类似,用require语句将其引入就可以:
var formidable = require("formidable");
这里该模块做的就是将通过HTTP POST请求提交的表单,在Node.js中能够被解析。

我们要做的就是创建一个新的IncomingForm,它是对提交表单的抽象表示。之后。就能够用它解析request对象,获取表单中须要的数据字段。

node-formidable官方的样例展示了这两部分是怎样融合在一起工作的:
server.js
var formidable = require('formidable'),
     http = require('http'),
     sys = require('sys');
http.createServer(function(req, res){
     if (req.url == '/upload' && req.method.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;
     }
     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>'
          ].join('')
     );
}).listen(8888);
假设我们将上述代码,保存到一个文件里,并通过node来运行,就能够进行简单的表单提交了,包含文件上传。然后。能够看到通过调用form.parse传递给回调函数的files对象的内容。例如以下所看到的:
received upload:
{
     fields: {title: 'Hello World'},
     files: {
          upload: {
               size: 1558,
               path: '/tmp/1c747974a27a6292743669e91f29350b',
               name: 'us-flag.png',
               type: 'image/png',
               lastModifiedDate: Tue, 21 Jun 2011 07:02:41 GMT,
               _writeStream: [Object],
               length: [Getter],
               filename: [Getter],
               mime: [Getter]
          }
     }
}
为了实现我们的功能。我们须要将上述代码应用到我们的应用中,另外。我们还要考虑怎样将上传文件的内容(保存在/tmp文件夹中)显示到浏览器中。
我们先来解决后面那个问题:对于保存在本地硬盘的文件,怎样才干在浏览器中看到呢?
显然。我们须要将该文件读取到我们的server中。使用一个叫fs的模块。

我们来加入/showURL的请求处理程序,该处理程序直接硬编码将文件/tmp/test.png内容展示到浏览器中。当然了。首先须要将该图片保存到这个位置才行。
将requestHandlers.js改动为例如以下形式:
var querystring = require("querystring"),

    fs = require("fs");
function start(response, postData) {

     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\' method=\'post\'>',

               '<textarea name=\'text\' rows=\'20\' cols=\'60\'></textarea>',

               '<input type=\'submit\' value=\'Submit text\' />',

          '</form>',

               '</body>',

          '</html>'

     ].join("");

     response.writeHead(200, {"Content-Type": "text/html"});

     response.write(body);

     response.end();

}
function upload(response, postData) {

     console.log("Request handler 'upload' was called.");

     response.writeHead(200, {"Content-Type": "text/plain"});

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

     response.end();

}
function show(response, postData) {

    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);
重新启动server之后。通过訪问http://localhost:8888/show,就能够看到保存在/tmp/test.png的图片了。
最后我们须要做的是:
在/start表单中加入一个文件上传元素
将node-formidable整合到我们的upload请求处理程序中。用于将上传的图片保存到/tmp/test.png
将上传的图片内嵌到/uploadURL输出的HTML中
第一项非常easy。仅仅须要在HTML表单中,加入一个multipart/form-data的编码类型。移除此前的文本区。加入一个文件上传组件,并将提交button的文案改为"Upload file"就可以。

例如以下requestHandler.js所看到的:

var querystring = require("querystring"),

    fs = require("fs");
function start(response, postData) {

     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>'

     ].join("");

     response.writeHead(200, {"Content-Type": "text/html"});

     response.write(body);

     response.end();

}
function upload(response, postData) {

     console.log("Request handler 'upload' was called.");

     response.writeHead(200, {"Content-Type": "text/plain"});

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

     response.end();

}
function show(response, postData) {

    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;
非常好。下一步相对照较复杂。这里有这样一个问题:我们须要在upload处理程序中对上传的文件进行处理,这种花。我们就须要将request对象传递给node-formidable的form.parse函数。

可是,我们有的仅仅是response对象和postData数组。

看样子,我们仅仅能不得不将request对象从server開始一路通过请求路由,再传递给请求处理程序。也许还有更好的方案,可是。无论怎么说,眼下这样做能够满足我们的需求。

到这里,我们能够将postData从server以及请求处理程序中移除了一方面。对于我们处理文件上传来说已经不须要了,另外一方面,它甚至可能会引发这样一个问题:我们已经“消耗”了request对象中的数据。这意味着,对于form.parse来说,当它想要获取数据的时候就什么也获取不到了。(由于Node.js不会对数据做缓存)
我们从server.js開始:移除对postData的处理以及request.setEncoding(这部分node-formidable自身会处理),转而採用将request对象传递给请求路由的方式:
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我们不再须要传递postData了,这次要传递request对象:
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/html"});
          response.write("404 Not found");
          response.end();
     }
}
exports.route = route;
如今,request对象就能够在我们的upload请求处理程序中使用了。

node-formidable会处理将上传的文件保存到本地/tmp文件夹中。而我们须要做的是确保该文件保存成/tmp/test.png。没错,我们保持简单,并如果仅仅同意上传PNG图片。

这里採用fs.renameSync(path1, path2)来实现。要注意的是,正如其名,该方法是同步运行的,也就是说。假设该重命名的操作非常耗时的话会堵塞。这块我们先不考虑。
接下来。我们把处理文件上传以及重命名的操作放到一起,例如以下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>'

     ].join("");

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

     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, postData) {

    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;
好了,重新启动server,我们应用全部的功能就能够用了。选择一张本地图片,将其上传到server,然后浏览器就会显示该图片。

总结与展望
我们开发完了一个Node.js的web应用,应用虽小。但却"五脏俱全"。

期间,我们介绍了非常多技术点:服务端JavaScript、函数式编程、堵塞与非堵塞、回调、事件、内部和外部模块等待。

还有本书没有介绍到的,怎样操作数据库、怎样进行单元測试、怎样开发Node.js的外部模块以及一些简单的诸如怎样获取GET请求之类的方法。

有什么问题能够向社区寻求解答。
參考地址:https://github.com/joyent/node/wiki

nodejs具体解释的更多相关文章

  1. Nodejs V8引擎 fast property lookup

    前言 之所以会研究这个东西,是我在网上找了一下各个语言的执行效率比较.好吧,我承认这是个无聊的东西,不过看看总是无妨,然而我惊讶的发现,有些测试声称Java,C,Nodejs是处在同一个效率级别参见链 ...

  2. 用做网页开发经历了三个阶段(附长篇讨论) good

    用做网页开发经历了三个阶段:第一阶:傻干阶段使用Intraweb,傻瓜型,无需知道javascript,html,css,会pascal就可以了. 第二阶:困惑阶段使用Intraweb,有很多限制,比 ...

  3. vue.js+webpack在一个简单实例中的使用过程demo

    这里主要记录vue.js+webpack在一个简单实例中的使用过程 说明:本次搭建基于Win 7平台 Node.js 安装官网提供了支持多种平台的的LTS版本下载,我们根据需要来进行下载安装.对于Wi ...

  4. nodejs、npm、grunt——名词解释

    最近着手开发一个新项目,打算从工程化的角度整理一套自己的前端开发.发布体系. grunt这些工具,之前别人用我也用,并没有认真想过它们的前世今生,正好趁着这个机会,我来理一理目前业界比较流行这些工具的 ...

  5. nodejs入门篇---创建project并具体解释

    想了非常久.总想写点对大家有优点的,今天解说生成项目. 如今市面上一般须要人全栈-----mean(mongo,express.angular,nodejs),这样能够从前端开发到后端以及数据库,听起 ...

  6. nodejs系列(二)REPL交互解释 事件循环

    一.REPL交互解释 命令行中输入node启动REPL: > var x =2;undefined> do{x++;... console.log("x:="+x);. ...

  7. NodeJS——模块全局安装路径配置以及关于supervisor的问题解释

    下载安装NodeJS后,在自己选择的路径下会有如下的文件: 默认情况下NodeJS安装会同时安装npm(模块管理器:用于管理用户require的模块,有全局和本地两种). 注:全局:执行npm  in ...

  8. nodejs package.json解释

    { "name": "node-echo", # 包名,在NPM服务器上须要保持唯一 "version": "1.0.0" ...

  9. [NodeJS] 优缺点及适用场景讨论

    概述: NodeJS宣称其目标是“旨在提供一种简单的构建可伸缩网络程序的方法”,那么它的出现是为了解决什么问题呢,它有什么优缺点以及它适用于什么场景呢? 本文就个人使用经验对这些问题进行探讨. 一. ...

随机推荐

  1. Hadoop 基本架构

    Hadoop 由两部分组成,分别是分布式文件系统和分布式计算框架 MapReduce. 其中分布式文件系统主要用于大规模数据的分布式存储,而 MapReduce 则构建在分布式文件系统之上,对存储在分 ...

  2. office excel2013如何启用solver选项

    Excel要启用solver很多地方说是要单独安装插件,我认为不同版本可能操作不同.此时office2013已经足够强大,可以通过下面的方法来启用solver 1:在office2013 Excel中 ...

  3. php的opcache缓存扩展(php页面代码刷新速度)

    opcache (全程 zend opcache): 从php5.5开始,默认提供的php脚本缓存扩展,编译php5.5时加上参数--enable-opcache就可以编译opcache了,只是要启用 ...

  4. 反编译示例:mxd检查

    gisoralce在博客园发布了一个mxd检查工具,主要功能是将arcgis的mxd数据源有效性(含矢量和影像)检查.检查是否为相对路径,自动保存为相对路径. 这是一个未加壳的.NET程序,正好拿来练 ...

  5. double转成string时,不以科学计数法表示

    用gson解析json串的时候,经常会自动把我想用string表示的内容转换成double,并且还是科学计数法,这一点也不科学,写个方法,格式化一下. public static String par ...

  6. [Oracle] SQL*Loader 详细使用教程(5)- 典型例子

    本文介绍SQL*Loader在实际使用过程中经常用到的典型例子. 1. 表中的列比数据文件的列要少怎么办? 假设一个csv的文件如下: a1,a2,a3,a4 b1,b2,b3,b4 c1,c2,c3 ...

  7. javascript - return

    return 使用,建议使用vsCode编译器. /** * return:中断语句运行. * * 1.return;和return false是一样的 * 2.return只能返回一个参数,可以是值 ...

  8. edge中断分析

    眼下正在调试msix中断,在測试过程中发现会概率性的丢失中断.Msix中断默认是edge触发的中断,edge触发的中断是在中断相应pin发生电平信号跳变的时候,会发出一个中断请求. 由于跳变是一瞬间的 ...

  9. Unity3D For Android 开发教程

    原地址:http://game.ceeger.com/Unity/Doc/2011/Unity3D_For_Android.html 我自认为抵挡诱惑的能力还是很强大的,关键的时候还能把持住自己.今天 ...

  10. 你远比想象中强大pdf

      读后感: 序 一.强化自我认知 认识你自己 你认为什么东西是最重要的呢? 这个问题的答案就是价值观. 让定期审视人生成为习惯 除去恐惧 树立目标 二.改变思维模式 选择,记住你的选择(做决定) 巅 ...