什么是Node.js
Node.js 是服务器端的 JavaScript 运行环境,它允许在后端(脱离浏览器环境)运行JavaScript代码。Node.js具有无阻塞(non-blocking)和事件驱动(event-driven)等的特色,它实现了类似 Apache 和 nginx 的web服务,让你可以通过它来搭建基于 JavaScript 的 Web App。
要实现在后台运行JavaScript代码,代码需要先被解释然后正确的执行,Node.js的原理正是如此,它使用了Google的V8虚拟机(Google的Chrome浏览器使用的JavaScript执行环境),来解释和执行JavaScript代码。
除此之外,伴随着Node.js的还有许多有用的模块,它们可以简化很多重复的劳作,比如向终端输出字符串。 因此,Node.js事实上既是一个运行时环境,同时又是一个库。
关于如何安装Node.js,这里就不赘述,可以直接参考官方的安装指南。安装完成后,继续以下内容。
第一个Node.js应用:"Hello World"
打开编辑器创建一个helloworld.js文件,写入向STDOUT输出"Hello World"的代码:
代码如下 | 复制代码 |
console.log("Hello World"); |
保存该文件,并通过Node.js来执行:
node helloworld.js终端将输出Hello World 。
一个基于Node.js的web应用实例的需求
· 用户可以通过浏览器使用我们的应用。
· 当用户请求http://domain/start时,可以看到一个欢迎页面,页面上有一个文件上传的表单。
· 用户可以选择一个图片并提交表单,随后文件将被上传到http://domain/upload,该页面完成上传后会把图片显示在页面上。
应用不同模块分析
· 我们需要提供Web页面,因此需要一个HTTP服务器
· 对于不同的请求,根据请求的URL,我们的服务器需要给予不同的响应,因此我们需要一个路由,用于把请求对应到请求处理程序(request handler)
· 当请求被服务器接收并通过路由传递之后,需要可以对其进行处理,因此我们需要最终的请求处理程序
· 路由还应该能处理POST数据,并且把数据封装成更友好的格式传递给请求处理入程序,因此需要请求数据处理功能
· 我们不仅仅要处理URL对应的请求,还要把内容显示出来,这意味着我们需要一些视图逻辑供请求处理程序使用,以便将内容发送给用户的浏览器
· 最后,用户需要上传图片,所以我们需要上传处理功能来处理这方面的细节
使用PHP的话一般来说会用一个Apache HTTP服务器并配上mod_php5模块。从这个角度看,整个"接收HTTP请求并提供Web页面"的需求根本不需要PHP来处理。
不过对Node.js来说,概念完全不一样了。使用Node.js时,我们不仅仅在实现一个应用,同时还实现了整个HTTP服务器。事实上,我们的Web应用以及对应的Web服务器基本上是一样的。似乎要做一大堆东西,但对Node.js来说这并不是什么麻烦的事。
首先从第一个部分——HTTP服务器着手:
构建应用的模块 一个基础的HTTP服务器
为了保持代码的可读性,把不同功能的代码放入不同的模块中,这样可以实现代码分离。这种方法允许你拥有一个干净的主文件(main file),你可以用Node.js执行它;同时你可以拥有干净的模块,它们可以被主文件和其他的模块调用。
现在我们来创建一个用于启动我们的应用的主文件,和一个保存着我们的HTTP服务器代码的模块。
先从服务器模块开始,在项目的根目录下创建一个叫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); /* 也可写成以下形式 var http = require("http"); function onRequest(request, response) { http.createServer(onRequest).listen(8888); |
*/这样就搭建了一个可以工作的HTTP服务端。为了证明这一点,我们来运行并且测试这段代码。首先,用Node.js执行你的脚本:
node server.js接下来,打开浏览器访问http://localhost:8888/,你会看到一个写着"Hello World"的网页。
分析HTTP服务器
接下来分析一下这个HTTP服务器的构成。
第一行请求(require)Node.js自带的 http 模块,并且把它赋值给 http 变量。
接下来我们调用http模块提供的函数: createServer 。这个函数会返回一个对象,这个对象有一个叫做 listen 的方法,这个方法有一个数值参数,指定这个HTTP服务器监听的端口号。
本来可以用这样的代码来启动服务器并侦听8888端口:
代码如下 | 复制代码 |
var http = require("http"); var server = http.createServer(); server.listen(8888); |
这段代码只会启动一个侦听8888端口的服务器,它不做任何别的事情,甚至连请求都不会应答。
基于事件驱动的回调
当使用 http.createServer 方法的时候,当然不只是想要一个侦听某个端口的服务器,我们还想要它在服务器收到一个HTTP请求的时候做点什么。
问题是,这是异步的:请求任何时候都可能到达,但是服务器却跑在一个单进程中。
写PHP应用的时候,我们一点也不为此担心:任何时候当有请求进入的时候,网页服务器(通常是Apache)就为这一请求新建一个进程,并且开始从头到尾执行相应的PHP脚本。那么在我们的Node.js程序中,当一个新的请求到达8888端口的时候,我们怎么控制流程呢?
这就是Node.js/JavaScript的事件驱动设计能够真正帮上忙的地方了——虽然我们还得学一些新概念才能掌握它。
先看看这些概念是怎么应用在我们的服务器代码里的:
我们创建了服务器,并且向创建它的方法传递了一个函数。无论何时我们的服务器收到一个请求,这个函数就会被调用。 我们不知道这件事情什么时候会发生,但是我们现在有了一个处理请求的地方:它就是我们传递过去的那个函数。至于它是被预先定义的函数还是匿名函数,就无关紧要了。这个就是回调 。我们给某个方法传递了一个函数,这个方法在有相应事件发生时调用这个函数来进行回调 。
让我们再来琢磨琢磨这个新概念。我们怎么证明,在创建完服务器之后,即使没有HTTP请求进来、我们的回调函数也没有被调用的情况下,我们的代码还继续有效呢?我们试试这个:
代码如下 | 复制代码 |
var http = require("http"); function onRequest(request, response) { http.createServer(onRequest).listen(8888); console.log("Server has started."); |
注意:在 onRequest (我们的回调函数)触发的地方,我用 console.log 输出了一段文本。在HTTP服务器开始工作之后,也输出一段文本。
当我们与往常一样,运行它node server.js时,它会马上在命令行上输出"Server has started."。当我们向服务器发出请求(在浏览器访问http://localhost:8888/),"Request received."这条消息就会在命令行中出现。这就是事件驱动的异步服务器端JavaScript和它的回调。(请注意,当我们在服务器访问网页时,我们的服务器可能会输出两次"Request received."。那是因为大部分服务器都会在你访问 http://localhost:8888 /时尝试读取 http://localhost:8888/favicon.ico )
服务器是如何处理请求的
接下来简单分析一下服务器代码中剩下的部分,也就是我们的回调函数 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 模块所提供的公共方法的对象。
给这种本地变量起一个和模块名称一样的名字是一种惯例,但是你也可以按照自己的喜好来:
代码如下 | 复制代码 |
var foo = require("http"); ... foo.createServer(...); |
怎么使用Node.js内部模块已经很清楚了,那怎么创建自己的模块,又怎么使用它呢?
事实上,我们不用做太多的修改。把某段代码变成模块意味着我们需要把我们希望提供其功能的部分导出到请求这个模块的脚本。
目前,我们的HTTP服务器需要导出的功能非常简单,因为请求服务器模块的脚本仅仅是需要启动服务器而已。
我们把服务器脚本放到一个叫做 start 的函数里,然后会导出这个函数。
代码如下 | 复制代码 |
var http = require("http"); function start() { http.createServer(onRequest).listen(8888); |
exports.start = start;虽然服务器的代码还在 server.js 中,但现在就可以创建我们的主文件 index.js 并在其中启动HTTP了,。
创建 index.js 文件并写入以下内容:
代码如下 | 复制代码 |
var server = require("./server"); |
server.start();我们可以像使用任何其他的内置模块一样使用server模块:请求这个文件并把它指向一个变量,其中已导出的函数就可以被我们使用了。
现在就可以从我们的主要脚本启动我们的应用了:
node index.js现在可以把应用的不同部分放入不同的文件里,并且通过生成模块的方式把它们连接到一起了。
我们仍然只拥有整个应用的最初部分:我们可以接收HTTP请求。但是我们得做点什么——对于不同的URL请求,服务器应该有不同的反应。
对于一个非常简单的应用来说,你可以直接在回调函数 onRequest() 中做这件事情。不过就像我说过的,我们应该加入一些抽象的元素,让我们的例子变得更有趣一点儿。
处理不同的HTTP请求在我们的代码中是一个不同的部分,叫做"路由选择"——那么,我们接下来就创造一个叫做 路由 的模块吧。
首页 1 2 3 4 5 6 7 8 末页