入门作者manuel kiessling翻译gothe beginner book中文版_第1页
入门作者manuel kiessling翻译gothe beginner book中文版_第2页
入门作者manuel kiessling翻译gothe beginner book中文版_第3页
入门作者manuel kiessling翻译gothe beginner book中文版_第4页
入门作者manuel kiessling翻译gothe beginner book中文版_第5页
已阅读5页,还剩36页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

Node入关本书致力于教会你如何用Node.js来开发应用,过程中会传授你所有所需的“高级Node入关本书致力于教会你如何用Node.js来开发应用,过程中会传授你所有所需的“高级at知识。本书绝不是一本“HelloWorld”的教程。作者Manuel翻译goddyzhao&GrayZhang&状你正在阅读的已经是本书的最终版。因此,只有当进行错误更正以及针对新版本Node.js动进行对应的修正时,才会进行更新。本书中的代码案例都在 读者对本书最适合与我有相似技术背景的读者:至少对一门诸如Ruby、Python、PHP或者Java这样这里指的适合对其他编程语言有一定经验的开发者,意思是说,本书不会对诸如数据类型、变量、控制结构等等之类非常基础的概念作介绍。要读懂本书,这些基础的概念我都默认你已经会了。然而,本书还是会对JavaScript中的函数和对象作详细介绍,因为它们与其他同类编程语言中的函数和对象有很大的不同。本书结读完本书之后,你将完成一个完整的web当然了,应用本身并没有什么了不起的,相比为了实现该功能书写的代码本身,我们更关注的是如何创建一个框架来对我们应用的不同模块进行干净地剥离。是不是很玄乎?稍后你就明白了。本书先从介绍在Node.js环境中进行JavaScript开发和在浏览器环境中进行JavaScript异开始。World”应用,这也是最基础的Node.js可以确保的是,在这过程中,大家会学到JavaScrip可以确保的是,在这过程中,大家会学到JavaScript中一些高级的概念、如何使用它们以及为什么使用这些概念就可以实现而其他编程语言中同类的概念就无法实现。本书Github目关状读者对本书结JavaScript与服务器端“Hello一个完整的基于Node.js的web应用应用不同模块分构建应用的模进行函数传如何来进行请求的“路由行为驱动执行为驱动执以非阻塞操作进行请求响更有用的场处理POST请处理文件上总结与展JavaScript与JavaScript与抛开技术,我们先来聊聊你以及你和JavaScript的关系。本章的主要目的是想让你看看,对你而言是否有必要继续阅读后续章节的内容。而你真正想要的是“干货”,你想要知道如何构建复杂的web站点——于是,你学习了一种与此同时,你还始终关注着JavaScript,随着通过一些对jQuery,Prototype绍,你慢慢了解到了很多JavaScript中的进阶技能,同时也感受到了JavaScript绝非仅仅是window.open()那么简单。.不过,这些毕竟都是前端技术,尽管当想要增强页面的时候,使用jQuery总让你觉得很爽,但到最后,你顶多是个JavaScript用户,而非JavaScript开发者。然后,出现了Node.js,服务端的JavaScript,于是,你觉得是时候该重新拾起既熟悉又陌生的JavaScript了。但是别急,写Node.js件事情;理解为什么它们要以它们书写的这种方式来书写则意味着——你要懂JavaScript次是玩真的了。问题来了:件事情;理解为什么它们要以它们书写的这种方式来书写则意味着——你要懂JavaScript次是玩真的了。问题来了:由于JavaScript真正意义上以两种,甚至可以说是三种形态存在(从中世纪90年代的作为对DHTML进行增强的小玩具,到像jQuery那样严格意义上的前端技术,一直到现在的服务端技术),因此,很难找到一个“正确”的方式来学习JavaScript,使得让你书写Ns应用的时候感觉自己是在真正开发它而不仅仅是使用它。因为这就是关键:你本身已经是个有经验的开发者,你不想通过到处寻找各种解决方案(其中当然了,外面不乏很优秀的学习JavaScript的文章。但是,有的时候光靠那些文章是远远不够的。你需要的是指导。简短申业界有非常优秀的JavaScript我就是上一节中描述的那个我。我熟悉如何开发后端web应用,但是对“真正”的JavaScript以及Node.js,我都只是新手。我也只是最近学习了一些JavaScript的高级概念,并没有实践经验。如果成功的话,那么本书就是我当初开始学习Node.js服务端JavaScript是一门“完整”的语言:它可以使用在不同的上下文中,其能力与其他同类语言相Node.js事实上就是另外一种上下文,它允许在后端(脱离浏览器环境)运行JavaScript要实现在后台运行JavaScript代码,代码需要先被解释然后正确的执行。Node.js此,它使用了Google的V8虚拟机(Google的Chrome浏览器使用的JavaScript执行环境),来解释和执行JavaScript除此之外,伴随着Node.js的还有许多有用的模块,它们可以简化很多重复的劳作,比如向终端输出字符串。因此,Node.js要使用Node.js,首先需要进行安装。关于如何安装来解释和执行JavaScript除此之外,伴随着Node.js的还有许多有用的模块,它们可以简化很多重复的劳作,比如向终端输出字符串。因此,Node.js要使用Node.js,首先需要进行安装。关于如何安装Node.js,这里就不赘述了,可以直接参考官方的安装指南。安装完成后,继续回来阅读本书下面的内容。“Hello World”保存该文件,并通过Node.js正常的话,就会在终端输出HelloWorld一个完整的基于Node.js的web用nodeconsole.log("Hello当用户请求http://domain/start时,可以看到一个欢迎页面,页面上有一个文件上传的表单。用户可以选择一个图片并提交表单,随后文件将被上传到h当用户请求http://domain/start时,可以看到一个欢迎页面,页面上有一个文件上传的表单。用户可以选择一个图片并提交表单,随后文件将被上传到http://domain/upload面完成上传后会把图片显示在页面上。差不多了,你现在也可以去Google一下,找点东西乱搞一下来完成功能。但是我们现在先不做这个。应用不同模块分我们需要提供Web页面,因此需要一个HTTP个路由,用于把请求对应到请求处理程序(requesthandler)我们先来想想,使用PHP的话我们会怎么构建这个结构。一般来说我们会用一个ApacheHTTP不过对Node.js来说,概念完全不一样了。使用Node.js时,我们不仅仅在实现一个应用,同时还实现了整个HTTP服务器。事实上,我们的Web应用以及对应的Web服务器基本上是一样的。听起来好像有一大堆活要做,但随后我们会逐渐意识到,对Node.js来说这并不是什么麻烦的事。构建应用的模一个基础的HTTP构建应用的模一个基础的HTTP服务当我准备开始写我的第一个“真正的”Node.js应用的时候,我不但不知道怎么写Node.js代码,也不知道怎么组织这些代码。我应该把所有东西都放进一个文件里吗?网上有很多教程都会教你把所有的逻辑都放进一个用Node.js写的基础HTTP服务器里。但是如果我想加入更多的内容,同时还想保持代码的可读性呢?这种方法允许你拥有一个干净的主文件(mainfile),你可以用Node.js执行它;同时你可以那么,现在我们来创建一个用于启动我们的应用的主文件,和一个保存着我们的HTTP码的模块。在我的印象里,把主文件叫做inde.js或多或少是个标准格式。把服务器模块放进叫server.js的文件里则很好理解。让我们先从服务器模块开始。在你的项目的根目录下创建一个叫server.js的文件,并写入以下代码:搞定!你刚刚完成了一个可以工作的HTTP服务器。为了证明这一点,我们来运行并且测试这段代码。首先,用Node.js执行你的脚本:接下来,打开浏览器访问http://localhost:8888/,你会看到一个写着“HelloWorld”的网nodevarhttp=require("http");response.write("HelloWorld");这很有趣,不是吗?让我们先来谈谈HTTP服务器的问题,把如何组织项目的事情先放一边吧,你觉得如何?我保证之后我们会解决那个问题的。分析HTTP服务第一行请求(require)Node.js自带的http这很有趣,不是吗?让我们先来谈谈HTTP服务器的问题,把如何组织项目的事情先放一边吧,你觉得如何?我保证之后我们会解决那个问题的。分析HTTP服务第一行请求(require)Node.js自带的http模块,并且把它赋值给http变量。接下来我们调用http模块提供的函数:createServer。这个函数会返回一个对象,这个对象有一个叫做listen的方法,这个方法有一个数值参数,指定这个HTTP服务器监听的端口号。咱们暂时先不管http.createServer的括号里的那个函数定义。这段代码只会启动一个侦听8888端口的服务器,它不做任何别的事情,甚至连请求都不会应答。createSever()的第一个参数,一个函数定义。实际上,这个函数定义是createServer()的第一个也是唯一一个参数。因为在JavaScript中,进行函数传function{}functionexecute(someFunction,{varhttp=请仔细阅读这段代码!在这里,我们把say函数作为execute函数的第一个变量进行了传递。这里返回的不是say的返回值,而是say本身!sayexecutesomeFunction,execute可以通过调用someFunction()(带括号的形式)来使用say函数。当然,因为请仔细阅读这段代码!在这里,我们把say函数作为execute函数的第一个变量进行了传递。这里返回的不是say的返回值,而是say本身!sayexecutesomeFunction,execute可以通过调用someFunction()(带括号的形式)来使用say函数。当然,因为say有一个变量,execute在调用someFunction我们在execute接受第一个参数的地方直接定义了我们准备传递给execute的函数。用这种方式,我们甚至不用给这个函数起名字,这也是为什么它被叫做匿名函数这是我们和我所认为的“进阶”JavaScript的第一次亲密接触,不过我们还是得循序渐进。现在,我们先接受这一点:在JavaScript中,一个函数可以作为另一个函数接收一个参数。我们可以先定义一个函数,然后传递,也可以在传递参数的地方直接定义函数。函数传递是如何让HTTP服务器工作varhttp=require("http");response.write("HelloWorld");functionexecute(someFunction,{}execute(function(word){console.log(word)},}execute(say,现在它看上去应该清晰了很多:我们向createServer函数传递了一个匿名函数。基于事件驱动的现在它看上去应该清晰了很多:我们向createServer函数传递了一个匿名函数。基于事件驱动的回这个问题可不好回答(至少对我来说),不过这是Node.js原生的工作方式。它是事件驱动的,这也是它为什么这么快的原因。你也许会想花点时间读一下FelixGeisendörfer的大作Understandingnode.js,它介绍了一些这一切都归结于“Node.js是事件驱动的”这一事实。好吧,其实我也不是特别确切的了解这句话的意思。不过我会试着解释,为什么它对我们用Node.js写网络应用(Webbasedapplication)是有意义的。当我们使用http.createServer方法的时候,我们当然不只是想要一个侦听某个端口的服务写PHP应用的时候,我们一点也不为此担心:任何时候当有请求进入的时候,网页服务器(通常是Apache)就为这一请求新建一个进程,并且开始从头到尾执行相应的PHP脚本。那么在我们的Node.js程序中,当一个新的请求到达8888嗯,这就是Node.js/JavaScript的事件驱动设计能够真正帮上忙的地方了——虽然我们还得学一些新概念才能掌握它。让我们来看看这些概念是怎么应用在我们的服务器代码里的。varhttp=functiononRequest(request,{response.writeHead(200,{"Content­Type":}这个就是传说中的回调。我们给某个方法传递了一个函数,这个方法在有相应事件发生时调用这个函数来进行回调。至少对我来说,需要一些功夫才能弄懂它。你如果还是不太确定的话就再去读读Felix这个就是传说中的回调。我们给某个方法传递了一个函数,这个方法在有相应事件发生时调用这个函数来进行回调。至少对我来说,需要一些功夫才能弄懂它。你如果还是不太确定的话就再去读读Felix章。让我们再来琢磨琢磨这个新概念。我们怎么证明,在创建完服务器之后,即使没有HTTP来、我们的回调函数也没有被调用的情况下,我们的代码还继续有效呢?我们试试这个:注意:在onRequest(我们的回调函数)触发的地方,我用console.log输出了一段文本。当我们与往常一样,运行它nodeserver.js时,它会马上在命令行上输出“Serverhasstarted.”。当我们向服务器发出请求(在浏览器访问http://localhost:8888/),“Request这就是事件驱动的异步服务器端JavaScriptreceived.”http://localhost:8888http://localhost:8888/favicon.ico)varhttp=functiononRequest(request,{console.log("Requestreceived.");response.write("HelloWorld");}console.log("Serverhasstarted.");服务器是如何处理请求onRequest()的主体部分。服务器是如何处理请求onRequest()的主体部分。onRequest()requestresponse。它们是对象,你可以使用它们的方法来处理HTTP请求的细节,并且响应请求(的浏览器发回一些东西)。所以我们的代码就是:当收到请求时,使用response.writeHead()函数发送一个HTTP状态200和HTTP头的内容类型(content-type),使用response.write()函数在HTTP相应主体中发送文本“HelloWorld"。最后,我们调用response.end()完成响应。目前来说,我们对请求的细节并不在意,所以我们没有使用request对象。服务端的模块放在哪server.js文件中有一个非常基础的HTTP服务器代码,而且我提到通常我们会有一个叫index.js的文件去调用应用的其他模块(比如server.js中的HTTP服务器模块)来引导和启动应用。我们现在就来谈谈怎么把server.js变成一个真正的Node.js模块,使它可以被我们(还没动工)的index.js主文件使用。Node.js中自带了一个叫做“http”的模块,我们在我们的代码中请求它并把返回值赋给一个本地变量。这把我们的本地变量变成了一个拥有所有http模块所提供的公共方法的对象。varhttp=很好,怎么使用Node.js内部模块已经很清楚了。我们怎么创建自己的模块,又怎么使用它呢?等我们把server.js的部分导出到请求这个模块的脚本。很好,怎么使用Node.js内部模块已经很清楚了。我们怎么创建自己的模块,又怎么使用它呢?等我们把server.js的部分导出到请求这个模块的脚本。目前,我们的HTTP服务器需要导出的功能非常简单,因为请求服务器模块的脚本仅仅是需要启动服务器而已。这样,我们现在就可以创建我们的主文件index.js并在其中启动我们的HTTP了,虽然服务器的代码还在server.js中。创建index.js文件并写入以下内容:functionstart(){functiononRequest(request,{console.log("Requestreceived.");response.write("HelloWorld");}console.log("Serverhasstarted.");}exports.start=varfoo=正如你所看到的,我们可以像使用任何其他的内置模块一样使用server模块:请求这个文件并把它指向一个变量,其中已导出的函数就可以被我们使用了。我们仍然只拥有整个应用的最初部分:我们可以接收HTTP请求。但是我们得做点什么——对于不同的URL请求,服务器应该有不同的反应。对于一个非常简单的应用来说,你可以直接在回调函数onRequest()中做这件事情。不过就像处理不同的H正如你所看到的,我们可以像使用任何其他的内置模块一样使用server模块:请求这个文件并把它指向一个变量,其中已导出的函数就可以被我们使用了。我们仍然只拥有整个应用的最初部分:我们可以接收HTTP请求。但是我们得做点什么——对于不同的URL请求,服务器应该有不同的反应。对于一个非常简单的应用来说,你可以直接在回调函数onRequest()中做这件事情。不过就像处理不同的HTTP请求在我们的代码中是一个不同的部分,叫做“路由选择”——那么,我们接下来就创造一个叫做路由的模块吧。如何来进行请求的“路由我们要为路由提供请求的URL和其他需要的GET及POST参数,随后路由需要根据这些数据来执行相应的代码(这里“代码”对应整个应用的第三部分:一系列在接收到请求时真正工作的处理程序)。因此,我们需要查看HTTP请求,从中提取出请求的URL以及GET/POST参数。这一功能应当属于路由还是服务器(甚至作为一个模块自身的功能)确实值得探讨,但这里暂定其为我们的HTTP服务器的功能。我们需要的所有数据都会包含在request对象中,该对象作为onRequest()回调函数的第一个参数传递。但是为了解析这些数据,我们需要额外的Node.JS模块,它们分别是url和querystring模块。node当然我们也可以用querystring模块来解析POST现在我们来给onRequest()函数加上一些逻辑,用来找出浏览器请求的URL在我们所要构建的应用中,这意味着来自/start和当然我们也可以用querystring模块来解析POST现在我们来给onRequest()函数加上一些逻辑,用来找出浏览器请求的URL在我们所要构建的应用中,这意味着来自/start和/upload的请求可以使用不同的代码来处理。稍后我们将看到这些内容是如何整合到一起的。现在我们可以来编写路由了,建立一个名为router.jsfunctionroute(pathname)console.log("Abouttoroutearequestfor"+}varurl=require("url");functionstart()functiononRequest(request,response)varpathname=url.parse(request.url).pathname;console.log("Requestfor"+pathname+"received.");response.write("HelloWorld");}console.log("Serverhasstarted.");}exports.start= ­­­­­­­­­­­­­­­­­­­­­­­­ 我们的服务器应当知道路由的存在并加以有效利用。我们当然可以通过硬编码的方式将这一依赖项绑定到服务器上,但是其它语言的编程经验告诉我们这会是一件非常痛苦的事,因此我们将使用依赖注入的方式较松散地添加路由模块(你可以读读MartinFow我们的服务器应当知道路由的存在并加以有效利用。我们当然可以通过硬编码的方式将这一依赖项绑定到服务器上,但是其它语言的编程经验告诉我们这会是一件非常痛苦的事,因此我们将使用依赖注入的方式较松散地添加路由模块(你可以读读MartinFowlers关于依赖注入的大作来作为背景知识)。首先,我们来扩展一下服务器的start()varrouter=varurl=require("url");functionstart(route)functiononRequest(request,response)varpathname=url.parse(request.url).pathname;response.write("HelloWorld");}console.log("Serverhasstarted.");}exports.start=exports.route=如果现在启动应用(nodeindex.js如果现在启动应用(nodeindex.js,始终记得这个命令行),随后请求一个URL,你将会看到应用输出相应的信息,这表明我们的HTTP服务器已经在使用路由模块了,并会将请求的路径传递给路由:(以上输出已经去掉了比较烦人的/favicon.ico请求相关的部分)行为驱动执将函数作为参数传递并不仅仅出于技术上的考量。对软件设计来说,这其实是个哲学问题。想想这样的场景:在index文件中,我们可以将router对象传递进去,服务器随后可以调用这个对象的route函数。我是在读了SteveYegge的大作名词王国中的死刑之后理解函数编程。你也去读一读这本书路由给真正的请求处理程回到正题,现在我们的HTTP服务器和请求路由模块已经如我们的期望,可以相互交流了,就像一对亲密无间的兄弟。当然这还远远不够,路由,顾名思义,是指我们要针对不同的URL有不同的处理方式。例如处理/start的“业务逻辑”就应该和处理/upload的不同。bash$nodeindex.jsAbouttoroutearequestfor应用程序需要新的部件,因此加入新的模块--已经无需为此感到新奇了。我们来创建一个叫做reque应用程序需要新的部件,因此加入新的模块--已经无需为此感到新奇了。我们来创建一个叫做requestHandlers的模块,并对于每一个请求处理程序,添加一个占位用函数,随后将这些函数作为模块的方法导出:在这里我们得做个决定:是将requestHandlers模块硬编码到路由里来使用,还是再添加一点依赖注入?虽然和其他模式一样,依赖注入不应该仅仅为使用而使用,但在现在这个情况下,使用依赖注入可以让路由和请求处理程序之间的耦合更加松散,也因此能让路由的重用性更高。那么我们要怎么传递这些请求处理程序呢?别看现在我们只有2个处理程序,在一个真实的应用中,请求处理程序的数量会不断增加,我们当然不想每次有一个新的URL或请求处理程序时,都要为了在路由里完成请求到处理程序的映射而反复折腾。除此之外,在路由里有一大堆ifrequest==xthencallhandlery也使得系统丑陋不堪。(associativearray)不过结果有点令人失望,JavaScriptfunctionstart()console.log("Requesthandler'start'was}functionupload()console.log("Requesthandler'upload'was}exports.start=start;在C++或C#中,当我们谈到对象,指的是类或者结构体的实例。对象根据他们实例化的模板(就是所谓的类),会拥有不同的属性和方法。但在JavaScript不是这个概念。在J在C++或C#中,当我们谈到对象,指的是类或者结构体的实例。对象根据他们实例化的模板(就是所谓的类),会拥有不同的属性和方法。但在JavaScript不是这个概念。在JavaScript中,对象就是一个键/值对的集合--你可以把JavaScript的对象想象成一个键为字符串类型的字典。但如果JavaScript的对象仅仅是键/值对的集合,它又怎么会拥有方法呢?好吧,这里的值可以是字符串、数字或者……函数!虽然handle并不仅仅是一个“东西”(一些请求处理程序的集合),我还是建议以一个动词作为其命名,这样做可以让我们在路由中使用更流畅的表达式,稍后会有说明。正如所见,将不同的URL映射到相同的请求处理程序上是很容易的:只要在对象中添加一个键为"/"的属性,对应requestHandlers.start即可,这样我们就可以干净简洁地配置/start都交由start这一处理程序处理。varurl=require("url");functionstart(route,handle)functiononRequest(request,response)varpathname=url.parse(request.url).pathname;route(handle,varrouter=varrequestHandlers=varhandle=handle["/"]=requestHandlers.start;handle["/start"]=requestHandlers.start;server.start(router.route,然后我们相应地在route.js文件中修改route()通过以上代码,我们首先检查给定的路径对应的请求处理程序是否存在,如果存在的话直接调用相应的函数。我们可以用从关联数组中获取元素一样的方式从传递的对象中获取请求处理函数,因此就有了简洁流畅的形如handle然后我们相应地在route.js文件中修改route()通过以上代码,我们首先检查给定的路径对应的请求处理程序是否存在,如果存在的话直接调用相应的函数。我们可以用从关联数组中获取元素一样的方式从传递的对象中获取请求处理函数,因此就有了简洁流畅的形如handle[pathname]();的表达式,这个感觉就像在前方中提到的那样:“嗨,请帮我处理了这个路径”。并且在浏览器中打开http://localhost:8888/可以看到这个请求同样被start请求处理程序处理了:ServerhasRequestfor/startAbouttoroutearequestfor/startRequesthandler'start'wasfunctionroute(handle,pathname)console.log("Abouttoroutearequestfor"+pathname);if(typeofhandle[pathname]==='function'){}elseconsole.log("Norequesthandlerfoundfor"+}}exports.route=response.write("HelloWorld");}console.log("Serverhasstarted.");}exports.start=让请求处理程序作出响让请求处理程序作出响这里要记住的是,浏览器发出请求后获得并显示的“HelloWorld”信息仍是来自于我们不好的实现方对于我们这样拥有PHP或者Ruby技术背景的开发者来说,最直截了当的实现方式事实上并不是非常靠谱:看似有效,实则未必如此。这里我指的“直截了当的实现方式”意思是:让请求处理程序通过onRequest(return())让我们从让请求处理程序返回需要在浏览器中显示的信息开始。我们需要将qtHls修改为如下形式:functionstart()return"HelloStart";}functionupload()return"HelloUpload";}exports.start=start;Requestfor/Abouttoroutearequestfor/最后,我们需要对我们的server.j最后,我们需要对我们的server.js进行重构以使得它能够将请求处理程序通过请求路由返回的内容响应给浏览器,如下所示:如果我们运行重构后的应用,一切都会工作的很好:请求http:/localhost:8888/start,浏览器会输出“HelloStart”,请求http://localhost:8888/upload会输出“HelloUpload”,而请求http://localhost:8888/foo会输出“404Notfound”。varurl=require("url");functionstart(route,handle)functiononRequest(request,response)varpathname=url.parse(request.url).pathname;varcontent=route(handle,pathname)}console.log("Serverhasstarted.");}exports.start=functionroute(handle,pathname)console.log("Abouttoroutearequestfor"+pathname);if(typeofhandle[pathname]==='function'){return}elsereturn"404Notfound";}}exports.route=阻塞与非阻阻塞与非阻这里,我们来修改下start请求处理程序,我们让它等待10秒以后再返回“HelloStart”。因(当然了,这里只是模拟休眠10秒,实际场景中,这样的阻塞操作有很多,比方说一些长时间的计算操作等。)如往常一样,我们先要重启下服务器。为了看到效果,我们要进行一些相对复杂的操作(functionstart()console.log("Requesthandler'start'wasfunctionsleep(milliSeconds)varstartTime=newwhile(newDate().getTime()<startTime+}return"Hello}functionupload()return"HelloUpload";}exports.start=start;我一起做):首先,打开两个浏览器窗口或者标签页。在第一个浏览器窗口的地址栏中输入http://localhost:8888/start,但是先不要打开它!在第二个浏览器窗口的地址栏中输入http://localhost:8888/upload,同样的,先不要打开接下来,做如下操作:在第一个窗口中(“/start”)按下回车,然后快速切换到第二个窗口中(“/upload我一起做):首先,打开两个浏览器窗口或者标签页。在第一个浏览器窗口的地址栏中输入http://localhost:8888/start,但是先不要打开它!在第二个浏览器窗口的地址栏中输入http://localhost:8888/upload,同样的,先不要打开接下来,做如下操作:在第一个窗口中(“/start”)按下回车,然后快速切换到第二个窗口中(“/upload”)按下回车。注意,发生了什么:/startURL加载花了10秒,这和我们预期的一样。但是,/uploadURL居这到底是为什么呢?原因就是start()包含了阻塞操作。形象的说就是“它阻塞了所有其他的处理工作”。这显然是个问题,因为Node一向是这样来标榜自己的:“在node中除了代码,所有一切都是并行执行的”。这句话的意思是说,Node.js——Node.js是单线程的。它通过事件轮询(eventloop)来实现并行操作,对此,我们应该要充分利用这一点——尽可能的避免阻塞操作,取而代之,多使用非阻塞操作。对于Node.js来说,它是这样处理的:“嘿,probablyExpensiveFunction()(的就是需要花时间处理的函数),你继续处理你的事情,我(Node.js线程)先不等你了,我继续去处理你后面的代码,请你提供一个callbackFunction(),等你处理完之后我会去调用该回调函数的,谢谢!”(如果想要了解更多关于事件轮询细节,可以阅读Mixu的博文——理解node.js询。)varexec=上述代码中,我们引入了一个新的Node.js模块,child_process。之所以用它,是为了实现一个既简单又实用的非阻塞操作:exe()。上述代码中,我们引入了一个新的Node.js模块,child_process。之所以用它,是为了实现一个既简单又实用的非阻塞操作:exe()。exe()做了什么呢?它从Node.js来执行一个shell命令。在上述例子中,我们用它来获取当前目录下所有的文件(“ls-lah”),然后,当/startURL请求的时候将文件信息输出到浏览器中。content(初始值为“empty”),执行“ls和往常一样,我们启动服务器,然后访问“http://localhost:8888/start”。之后会载入一个漂亮的web页面,其内容为“empty”这个时候,你可能大致已经猜到了,exec()在非阻塞这块发挥了神奇的功效。它其实是个很好的东西,有了它,我们可以执行非常耗时的shell操作而无需迫使我们的应用停下来等待该操作。(如果想要证明这一点,可以将“ls-lah”换成比如“find/”这样更耗时的操作来效果)functionstart()varcontent="empty";exec("ls­lah",function(error,stdout,{content=return}functionupload()return"HelloUpload";}exports.start=start;问题就在于,为了进行非阻塞工作,exec()在我们的例子中,该回调函数就是作为第二个参数传递给exec()Node.jsreturncontent,content仍然是“empty”,因为传递给问题就在于,为了进行非阻塞工作,exec()在我们的例子中,该回调函数就是作为第二个参数传递给exec()Node.jsreturncontent,content仍然是“empty”,因为传递给我们这里“lslah”的操作其实是非常快的(除非当前目录下有上百万个文件)。这也是为什么回调函数也会很快的执行到——不过,不管怎么说它还是异步的。为了让效果更加明显,我们想象一个更耗时的命令:“find/”,它在我机器上需要执行1分钟左右的时间,然而,尽管在请求处理程序中,我把“lslah”换成“find/”,当打开/startURL的时候,依然能够立即获得HTTP响应——很明显,当exec()在后台执行的时候,在“find以非阻塞操作进行请求响我刚刚提到了这样一个短语——“正确的方式”。而事实上通常“正确的方式”一般都不简不过,用Node.js就有这样一种实现方案 函数传递。下面就让我们来具体看看如何实现到目前为止,我们的应用已经可以通过应用各层之间传递值的方式(请求处理程序->请求路由->服务器)将请求处理程序返回的内容(请求处理程序最终要显示给用户的内容)传递给服务器“传递”给内容的方式。从实践角度来说,就是将response对象(从服务器的回调函function(error,stdout,{content=}数onRequest()获取)通过请求路由传递给请求处理程序。随后,处理程序就可以采用该对象相对此前从route()函数获取返回值的做法,这次我们将r数onRequest()获取)通过请求路由传递给请求处理程序。随后,处理程序就可以采用该对象相对此前从route()函数获取返回值的做法,这次我们将response对象作为第三个参数传递给route()函数,并且,我们将onRequest()处理程序中所有有关response的函数调都移除,因为我们希望这部分工作让route()函数来完成。下面就来看看我们的同样的模式:相对此前从请求处理程序中获取返回值,这次取而代之的是直接传递response对象。functionroute(handle,pathname,{console.log("Abouttoroutearequestfor"+pathname);if(typeofhandle[pathname]==='function'){}elseconsole.log("Norequesthandlerfoundfor"+pathname);response.write("404Notfound");}}exports.route=varurl=require("url");functionstart(route,handle)functiononRequest(request,response)varpathname=url.parse(request.url).pathname;route(handle,pathname,}console.log("Serverhasstarted.");}exports.start=如果没有对应的请求处理器处理,我们就直接返回“404”最后,我们将requestHandler.jsstart处理程序在exe如果没有对应的请求处理器处理,我们就直接返回“404”最后,我们将requestHandler.jsstart处理程序在exe()的匿名回调函数中做请求响应的操作,而upload处理程序仍然是简单的回复“HelloWorld”,只是这次是使用response对象而已。这时再次我们启动应用 如果想要证明/start处理程序中耗时的操作不会阻塞对/upload请求作出立即响应的话,可以将requestHandlers.js修改为如下形式:functionstart(response){console.log("Requesthandler'start'wasexec("findfunction(error,stdout,stderr){}functionstart(response){console.log("Requesthandler'start'wasexec("ls­lah",function(error,stdout,"text/plain"});response.write(stdout);}functionupload(response)console.log("Requesthandler'upload'wascalled.");response.write("HelloUpload");}exports.start=start;更有用的场更有用的场服务器,请求路由以及请求处理程序都已经完成了,下面让我们按照此前的用例给网站添加交互:用户选择一个文件,上传该文件,然后在浏览器中看到上传的文件。为了保持简单,我们假设用户只会上传图片,然后我们应用将该图片显示到浏览器中。好,下面就一步步来实现,鉴于此前已经对JavaScript原理性技术性的内容做过大量介绍了,这次我们加快点速度。要实现该功能,分为如下两步:首先,让我们来看看如何处理POST请求(非文件上传),之第一,尽管在Node.js中处理基础的POST请求相对比较简单,但在这过程中还是能学到很多。第二,用Node.js来处理文件上传(multipartPOST请求)是比较复杂的,它不在本书的范畴,但,如何使用外部模块却是在本书涉猎内容之内。处理POST请考虑这样一个简单的例子:我们显示一个文本区(textarea)供用户输入内容,然后通过请求提交给服务器。最后,服务器接受到请求,通过处理程序将输入的内容展示到浏览器中。start请求处理程序用于生成带文本区的表单,因此,我们将requestHandlers.js式:functionupload(response)console.log("Requesthandler'upload'wascalled.");response.write("HelloUpload");}exports.start=start;好了,现在我们的应用已经很完善了,都可以获得威比奖(WebbyAward好了,现在我们的应用已经很完善了,都可以获得威比奖(WebbyAwards)了,哈哈。(译者注:威比奖是由国际数字艺术与科学学院主办的评选全球最佳网站的奖项,具体参见详细说明)通过在浏览器中访问http://localhost:8888/start就可以看到简单的表单了,要记得重启服务器哦!你可能会说:这种直接将视觉元素放在请求处理程序中的方式太丑陋了。说的没错,但是,我并不想在本书中介绍诸如MVC之类的模式,因为这对于你了解JavaScript或者Node.js环境来说没多大关系。/upload请求处理程序现在,我们已经是新手中的专家了,很自然会想到采用异步回调来实现非阻塞地处理POST求的数据。这里采用非阻塞方式处理是明智的,因为POST请求一般都比较“重”——用户可能会输入大functionstart(response)console.log("Requesthandler'start'was'charset=UTF­8"/>'+'<formaction="/upload"'<inputtype="submit"value="Submittext"/>'+}functionupload(response)console.log("Requesthandler'upload'wascalled.");response.write("HelloUpload");}exports.start=start;为了使整个过程非阻塞,Node.js会将POST数据拆分成很多小的数据块,然后通过触发特定的事件,将这些小数据块传递给回调函数。这里的特定的事件有da为了使整个过程非阻塞,Node.js会将POST数据拆分成很多小的数据块,然后通过触发特定的事件,将这些小数据块传递给回调函数。这里的特定的事件有data事件(表示新的小数据块到达了)以及end事件(表示所有的数据都已经接收完毕)。我们需要告诉Node.js当这些事件触发的时候,回调哪些函数。怎么告诉呢 我们通在request对象上注册监听器(listener)来实现。这里的request对象是每次接收到HTTP请求问题来了,这部分逻辑写在哪里呢?我们现在只是在服务器中获取到了request对象——我们并没有像之前response对象那样,把request对象传递给请求路由和请求处理程序。在我看来,获取所有来自请求的数据,然后将这些数据给应用层处理,应该是HTTP的事情。因此,我建议,我们直接在服务器中处理POST数据,然后将最终的数据传递给请求由和请求处理器,让他们来进行进一步的处理。因此,实现思路就是:将data和end事件的回调函数直接放在服务器中,在data事件回调中收集所有的POST数据,当接收到所有数据,触发end事件后,其回调函数调用请求路由,并将数据传递给它,然后,请求路由再将该数据传递给请求处理程序。varurl=require("url");functionstart(route,handle){{varpostData=varpathname=url.parse(request.url).pathname;postData+=postDataChunk;request.addListener("data",function(chunk)//calledwhenanewchunkofdatawasrequest.addListener("end",function()//calledwhenallchunksofdatahavebeen上述代码做了三件事情:首先,我们设置了接收数据的编码格式为UTF-8,了“data”事件的监听器,用于收集每次接收到的新数据块,并将其赋值给postD上述代码做了三件事情:首先,我们设置了接收数据的编码格式为UTF-8,了“data”事件的监听器,用于收集每次接收到的新数据块,并将其赋值给postData变量,最后,我们将请求路由的调用移到end事件处理程序中,以确保它只会当所有数据接收完毕后才触发,并且只触发一次。我们同时还把POST数据传递给请求路由,因为这些数据,请求处理程序会用到。上述代码在每个数据块到达的时候输出了日志,这对于最终生产环境来说,是很不好的(量可能会很大,还记得吧?),但是,在开发阶段是很有用的,有助于让我们看到发生了什么。再来点酷的。我们接下来在/upload页面,展示用户输入的内容。要实现该功能,我们需要将postData传递给请求处理程序,修改router.js为如下形式:functionroute(handle,pathname,response,{console.log("Abouttoroutearequestfor"+pathname);if(typeofhandle[pathname]==='function'){handle[pathname](response,}elseconsole.log("Norequesthandlerfoundfor"+pathname);response.write("404Notfound");}}exports.route=postDataChunk+request.addListener("end",}console.log("Serverhasstarted.");}exports.start=我们最后要做的是:当前我们是把请求的整个消息体传递给了请求路由和请求处理程序。我们应该只把POST我们最后要做的是:当前我们是把请求的整个消息体传递给了请求路由和请求处理程序。我们应该只把POST数据中,我们感兴趣的部分传递给请求路由和请求处理程序。在我们这个例子中,我们感兴趣的其实只是text字段。我们可以使用此前介绍过的querystringfunctionstart(response,postData){console.log("Requesthandler'start'was'charset=UTF­8"/>'+'<formaction="/upload"'<textareaname="text"rows="20"functionstart(response,{console.log("Requesthandler'start'was'charset=UTF­8"/>'+'<formaction="/upload"'<inputtype="submit"value="Submittext"/>'+}functionupload(response,{console.log("Requesthandler'upload'wascalled.");response.write("You'vesent:"+postData);}exports.start=start;处理文件上回到90年代,这个用例完全可以满足用于IPO的商业模型了,如今,我们通过它能学到这样两件事情:处理文件上回到90年代,这个用例完全可以满足用于IPO的商业模型了,如今,我们通过它能学到这样两件事情:如何安装外部Node.js模块,以及如何将它们应用到我们的应用中。这里我们要用到的外部模块是FelixGeisendörfer开发的node-formidable模块。它对解析上传的文件数据做了很好的抽象。其实说白了,处理文件上传“就是”处理POST数据——但npmoknpminstall}functionupload(response,{console.log("Requesthandler'upload'wascalled.");response.write("You'vesentthetext:"+}exports.start=start;现在我们就可以用formidable模块了——使用外部模块与内部模块类似,用现在我们就可以用formidable模块了——使用外部模块与内部模块类似,用require引入即可:这里该模块做的就是将通过HTTPPOST请求提交的表单,在Node.js中可以被解析。我们要做如果我们将上述代码,保存到一个文件中,并通过node来执行,就可以进行简单的表单提交了,包括文件上传。然后,可以看到通过调用form.parse传递给回调函数的files如下所示:http=require('http'),sys=if(req.url=='/upload'&&req.method.toLowerCase()=='post')//parseafilevarform=newformidable.IncomingForm();res.writeHead(200,{'content­type':'text/plain'});res.write('receivedupload:\n\n');}//showafileupload'<inputtype="text"'<inputtype="submit"value="Upload">'+varformidable=我们先来解决后面那个问题 对于保存在本地硬盘中的文件,如何才能在浏览器中看到呢显然,我们需要将该文件读取到我们的服务器中,使用一个叫fs我们先来解决后面那个问题 对于保存在本地硬盘中的文件,如何才能在浏览器中看到呢显然,我们需要将该文件读取到我们的服务器中,使用一个叫fs我们来添加/showURL的请求处理程序,该处理程序直接硬编码将文件/tmp/test.png到浏览器中。当然了,首先需要将该图片保存到这个位置才行。将requestHandlers.jsfs=require("fs");functionstart(response,{console.log("Requesthandler'start'was'<metahttp­equiv="Content­Type"'+'<formaction="/upload"'<inputtype="submit"value="Submittext"/>'+}received{fields:{title:'HelloWorld'},{{size:name:'us­flag.png',type:lastModifiedDate:Tue,21Jun201107:02:41length:[Getter],filename:[Getter],mime:[Getter]}}}重启服务器之后,通过访问http://localhost:88重启服务器之后,通过访问http://localhost:8888/show,就可以看到保存在/tmttg的图片了。在/start将node-formidable整合到我们的uploadvarrouter=varrequestHandlers=varhandle=handle["/"]=requestHandlers.start;handle["/start"]=requestHandlers.start;handle["/show"]=requestHandlers.show;server.start(router.route,functionupload(response,{console.log("Requesthandler'upload'wascalled.");response.write("You'vesentthetext:"+}functionshow(response,{console.log("Requesthandler'show'wascalled.");{if(error)response.write(error+"\n");}elseresponse.write(file,"binary");}}exports.start=start;exports.show=show;将上传的图片内嵌到/uploadURL输出的HTML第一项很简单。只需要在HTML表单中,添加一个mu将上传的图片内嵌到/uploadURL输出的HTML第一项很简单。只需要在HTML表单中,添加一个multipartform-data的编码类型,移除此前的文本区,添加一个文件上传组件,并将提交按钮的文案改为“Uploadfile”即可。如下requestHandler.jsfs=require("fs");functionstart(r

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论