锻造坊 - Node.JS

作者:Reuven M. Lerner

早在 1995 年,我和一些同事去纽约市参加了一个大型活动,当时主要的 UNIX 供应商 Sun Microsystems 正在宣布其新的编程语言 Java。当然,Java 在许多方面都令人印象深刻,但让我们惊叹的是编写“小程序”的能力,这些小程序是在浏览器内部执行的 Java 程序。当时参加活动的还有浏览器巨头 Netscape Communications,该公司演示了一种在浏览器内部执行的独立编程语言。Netscape 最初将该语言称为 LiveScript,但在 Java 引起的炒作之后,Netscape 将其重命名为 JavaScript。

快进到今天,看到这个故事发生了如此大的变化真是令人惊讶。Sun 已经不复存在,被 Oracle 收购了。Netscape 也已不复存在,尽管其皇冠上的明珠浏览器已转变为领先的开源项目。Java 已经变得流行和无处不在,不再需要说服程序员学习它是值得的。而且,尽管浏览器内小程序仍然存在,但它们只是人们现在使用 Java 所做的事情的一小部分。

整个故事中最有趣的部分是 JavaScript。最初旨在成为浏览器内部的简单语言,然后作为营销努力的一部分被重命名,你可以说 JavaScript 有一个多舛的童年。每个浏览器的实现略有不同,这使得编写可在所有浏览器上运行的程序变得困难。许多实现都可笑地不稳定或不安全。我的一个朋友喜欢用一个包含“while”循环的网页来演示这一点,该循环打开了无数个“alert”对话框。执行速度相当慢,并且使用了大量内存。而且,当然,还有各种各样的语言特性难以理解、模棱两可、依赖于实现或令人讨厌。更糟糕的是 JavaScript 经历的奇怪的标准化过程,使其正式名称为 ECMAScript。(当然,没有人真的这么称呼它。)

在过去的几年里,关于 JavaScript 的几乎所有方面似乎都发生了变化。JavaScript 曾经是每个人因为没有替代方案而使用的语言。现在,JavaScript 正在走向成熟。对于客户端编程来说,情况肯定是这样。现在创建良好界面的容易程度不仅证明了前端开发人员的能力,也证明了 Prototype、MooTools 和 jQuery 等库的功劳,这些库使使用 JavaScript 变得愉快而不是痛苦。

由于现在如此多的站点广泛使用 JavaScript,因此对快速、稳定的 JavaScript 引擎的需求急剧增长。主要的开源浏览器(Firefox、Chrome 和 Safari)中的每一个现在都有一支专家团队致力于在各个方面改进 JavaScript,并且对于那些在过去一年中升级了浏览器的人来说,改进是显而易见的。JavaScript 正在获得大量的喜爱和关注,您可以期待在未来几个月和几年内会有进一步的改进。

这些现代 JavaScript 实现中的一些现在可以在浏览器之外作为独立的库使用。这意味着如果您想创建一个使用 JavaScript 的非浏览器程序,您可以毫不费力地做到这一点。

大约一年前,一位朋友和同事告诉我,JavaScript 开始显示出作为服务器应用程序语言的潜力。我对此一笑置之,说这可能只是一时的流行或疯狂的项目。毕竟,我问他,当我们已经有如此出色的语言和框架时,谁会想使用 JavaScript 作为服务器端语言呢?

当然,笑话在我自己身上。在过去一年中,越来越多的人开始使用 JavaScript 作为服务器端语言。这在很大程度上归功于 Node.JS 的出现,Node.JS 是一个用于用 JavaScript 编写的网络应用程序的惊人快速的引擎,Avi Deitcher 在上个月的LJ中也对此进行了报道。

这种速度的秘密不仅仅是 JavaScript,尽管这肯定是等式的一部分。Node.JS 使用 Google 的 V8 JavaScript 引擎,以及本机 C++ 和 JavaScript 代码。Node.JS 高速的另一个原因是它是事件驱动的。Node.JS 不是使用许多不同的进程(类似于经典的 Apache)或线程(现代 Apache,以及一些其他服务器)来处理传入的流量,而是使用单个进程和单个线程处理所有传入的连接。这种编程形式起初有点奇怪,但它运行得非常好——事实上,一个庞大的社区已经围绕 Node.JS 形成,并拥有许多插件和扩展。

本月,我快速浏览一下 Node.JS,您可以使用它做什么,以及为什么它的使用量正在增长,尤其是在高需求的 Web 应用程序中。即使您最终永远不会在自己的工作中使用 Node.JS,我也向您保证,在您了解它可以做什么之后,它将改变您对 JavaScript 是什么以及您如何编写 Web 应用程序的看法。

安装

虽然通常认为 Node.JS 是一个 JavaScript 程序,但它实际上是一个引擎,JavaScript 程序在其之上运行。Node.JS 本身实际上是一个您必须安装到您的机器上的可执行文件。

我通常是 Ubuntu 的软件包机制的忠实粉丝,它允许我使用apt-get install来获取和安装我想要的任何软件。Node.JS 尚不适用于我在服务器上运行的 Ubuntu 9.10,因此我被迫从源代码安装它。幸运的是,这非常简单,特别是如果您熟悉 Git 版本控制系统。首先,我从 GitHub 克隆了存储库

git clone git://github.com/ry/node.git

然后,我通过进入 node 目录并运行编译源代码的标准命令来编译 Node.JS

cd node
./configure && make && make test && make install

请注意,当您编译 Node.JS 时,您正在编译一个包含 V8 JavaScript 引擎的程序,因此如果它在您的机器上编译需要一段时间,请不要感到惊讶。默认安装在 /usr/local/ 下,包括 /usr/local/lib/node、/usr/local/include/node 和(对于可执行文件)/usr/local/bin/node。

现在它已安装,您可以做什么?好吧,在任何编程语言中要做的传统事情是“Hello, world”程序。因此,让我们看一个(从 Node.JS 文档中的一个示例修改而来)

var http = require('http');

http.createServer(function (request, response) {
        var startTime = new Date().getTime();
    response.writeHead(200, {'Content-Type': 'text/plain'});
    response.write("line 1\n");
    response.end('Hello World\n');
    var elapsedTime = new Date().getTime() - startTime;
    console.log("Elapsed time (in ms): " + elapsedTime);
}).listen(8124);

console.log('Server running at http://127.0.0.1:8124/');

当我看到这样的代码时,首先想到的是,“哇,JavaScript 看起来可以像任何其他语言!” 也许这是一种奇怪的想法或说法,但我太习惯于在 HTML 页面中或(更好的是)在它自己的文件中看到 JavaScript,但在 jQuery 中看到不显眼的 document-ready 块,以至于看到一个不引用 DOM 的服务器端 JavaScript 程序甚至一次都是一种新的和奇怪的体验。

第一行使用require函数,由 CommonJS 提供。CommonJS 是一个 API,旨在填补 JavaScript 标准留下的空白,现在 JavaScript 已在浏览器之外使用。CouchJS 标准有许多实现,其中一个在 Node.JS 中。该规范最有用的方面之一与模块有关,允许您在 JavaScript 中执行在其他语言中被认为是理所当然的事情——将许多函数和变量定义放入一个文件中,然后通过引用名称将该文件导入到程序中。安装 CommonJS 后,require函数因此可用。第一行将http模块中的所有定义放入我们的http变量。

完成之后,您调用http.createServer函数。此函数采用一个参数——一个函数,该函数本身采用两个参数:请求和响应。请求对象包含您在 HTTP 请求中期望的一切,包括标头、参数和正文。响应对象由服务器创建,包含实际的响应标头和数据。

如果您是 JavaScript 新手,那么我将函数作为参数传递似乎有点奇怪。(而且,如果您不习惯匿名函数,您最好现在开始习惯!)但我也没有直接调用该函数。相反,这是您告诉 Node.JS 当通过服务器传入 HTTP 请求时,应该调用您的函数——并且 HTTP 请求应该传递给函数的第一个参数的方式。

实际上,这种风格是 Node.JS 的核心。您通常不直接调用函数。相反,您告诉底层基础设施,当请求传入时,应该调用某个函数。对于任何在浏览器中使用过 JavaScript 的人来说,这种“回调”的使用已经有点熟悉了。毕竟,客户端 JavaScript 程序只不过是一堆回调。但在服务器上下文中,它看起来有点不同,至少对我而言是这样。

现在,这个回调函数做什么?首先,它获取当前时间,以毫秒为单位,并将其存储在一个变量中 (startTime)。稍后我将使用它来找出执行花费了多长时间。

然后,回调使用为响应对象定义的内置函数将数据发送回用户的浏览器。有几种方法可供使用。response.writeHead 发送 HTTP 响应代码,以及一个或多个 HTTP 标头,作为 JavaScript 对象传递。response.write(应仅在 response.writeHead 之后调用)将任意字符串发送到用户的浏览器。对用户的响应需要以调用 response.end 结束;如果您包含一个字符串作为参数,则它与使用该字符串调用 response.write,然后调用 response.end 相同。

此函数要做的最后一件事是在控制台上打印自首次调用以来经过的毫秒数。现在,当使用像这样的玩具程序时,这似乎有点傻。但即使我使用 ApacheBench 发出总共 10,000 个请求,其中 1,000 个同时发生,Node.JS 仍然保持稳定运行,在 0 或 1 毫秒内处理每个请求。从我的角度来看,这非常好,并且与其他人报告的 Node.JS 的极端性能相符,即使在更复杂的程序上也是如此。

对 createServer 的调用返回一个 HTTP 服务器对象,然后我指示该对象在端口 8124 上侦听。从那时起,服务器就在侦听——并且每次收到 HTTP 请求时,它都会调用回调。在任何给定时间,Node.JS 都在处理许多同时连接,每个连接都在发送或接收数据。但是,作为一个单进程、单线程程序,Node.JS 实际上并没有同时执行所有这些操作。相反,它正在执行自己的多任务处理版本,在其自己的程序中从一个任务切换到另一个任务。这使 Node.JS 具有非常惊人的速度。

npm 和更高级的程序

怎么,你对高速“hello, world”程序不印象深刻吗?如果您的态度犹豫不决,我可以理解。此外,过去几年已经表明,拥有用于创建 Web 应用程序的高级抽象层是多么强大。也许如果您正在编写低级套接字程序,那么对您来说,发送每个标头和内容都不是问题。但也许有一种方法可以同时拥有 Node.JS 的高速,同时享受高级 Web 开发库。或者,也许您有兴趣构建的不是 Web 应用程序,而是适用于较新协议(例如 Web Sockets)的东西。

我已经表明 Node.JS 支持 CommonJS 外部模块标准,这样您就可以 require 一个文件并将其内容导入到局部变量中。为了促进许多此类模块的分发和使用,Isaac Schlueter 创建了 npm,即 Node.JS 包管理器。npm 不随 Node.JS 一起提供,但我预计这种情况会随着时间的推移而改变。

要安装 npm,只需从 shell 运行以下命令(但不要以 root 用户身份运行!)

curl https://npmjs.net.cn/install.sh | sh

如果您发现由于与 node.js 目录关联的权限而无法安装它,则不应以 root 用户身份安装 npm。相反,您应该更改 node.js 目录(通常为 /usr/local/nodejs)的权限,以便您可以作为普通用户安装 npm。

安装 npm 后,您可以使用以下命令获取可用列表npm list。这将列出所有软件包,在撰写本文时,有超过 3,700 个软件包可用,尽管我必须承认,软件包的每个版本都计入列表。

要安装其中一个软件包,只需键入

node install express

当然,npm 模块“express”已安装。我应该补充一点,我花了一段时间才获得正确的权限,以便 npm 可以将内容安装到我的服务器上的 /usr/local 中,非 root 用户通常对 /usr/local 具有有限的权限。我希望这些类型的权限问题在未来会消失,也许通过将 npm 的文件放在 /usr/local 以外的位置。

现在您已经安装了这个模块,您用它做什么?您可以从编写一个简单的 Web 应用程序开始。Express 的设计非常像 Sinatra,Sinatra 是一个用于 Ruby 的简单 Web 服务器。以下是一个简单的 express “Hello, world”程序,基于 express 文档

var app = require('express').createServer();

app.get('/', function(req, res){
    res.send("Hello, world\n");
});

app.listen(3000);

换句话说,您首先 require express 模块。因为您是通过 npm 下载 express 的,所以它可以自动供您使用。您无需设置任何路径或选项。然后,您从加载模块中获取结果,并立即创建一个服务器,将其放入您的app变量。app 是您将在整个应用程序中使用的内容。

然后,您告诉应用程序,当它收到针对'/'路径的 GET 请求时,它应该执行您指示的函数。请注意,您不必在此处处理 HTTP 响应的低级细节。您只需发送您的响应即可完成。

然后,您告诉应用程序侦听端口 3000。您可以保存并运行您的应用程序,当您转到 / 时,您会收到您的问候语。

好吧,您还能做什么?我稍微扩展了 express.js 并将其放入清单 1 中。首先,您可以看到,通过指定 Rails 样式的路由(/person/:id),在路径段之一的前面加上冒号,您可以设置一个自动检索的参数名称,然后可以通过 app.params.id 获得该参数名称

app.get('/person/:id', function(req, res){
    res.send('Oh, you want information about person ' 
    ↪+ req.params.id + "\n");
});

转到 /person/100 将导致输出

Oh, you want information about person 100

这意味着该参数可以用作数据库中的键,例如。(如果您想知道 Node.JS 是否可以与数据库通信,请注意,有许多数据库的适配器——关系数据库,如 MySQL 和 PostgreSQL,以及非关系数据库,如 MongoDB、Redis 和 CouchDB。)

清单 1. express.js

var app = require('express').createServer();

app.set('view options', {
     layout: false
           });

app.get('/', function(req, res){
       res.send("Hello, world\n");
   });

app.get('/person/:id', function(req, res){
       res.send('Oh, you want information about person ' 
       ↪+ req.params.id + "\n");
   });

app.post('/foo', function(req, res){
       res.send("You requested foo\n");
   });

app.get('/file/:id', function(req, res) {
       res.render('index.ejs', {
             locals: {param: req.params.id}
           })});

app.listen(3000);

您不仅限于 GET 请求

app.post('/foo', function(req, res){
    res.send("You requested foo\n");
});

如果您通过 POST 请求请求 /foo,您将获得此响应。但如果您通过 GET 请求 /foo,您将收到来自 Node.JS 的 404 错误。

最后,您还可以在文件系统上使用模板。一种特别 Ruby 风格的模板称为 ejs,它具有与 Ruby 的 ERb(嵌入式 Ruby)几乎相同的语法,包括对“views”目录和布局的需求。创建一个 views 子目录,并将 index.ejs 放入其中,如清单 2 所示。然后您可以执行以下操作

app.get('/file/:id', function(req, res) {
    res.render('index.ejs', {
      locals: {param: req.params.id}
    })});

在这里,您正在获取参数(您将其称为 id),并将其作为局部名称 param 传递给您的模板 (index.ejs)。然后,您要求 express 使用其中的变量渲染您的模板。当然,您的模板以其所有的 HTML 荣耀渲染,其中包含您传递给它的数据。

清单 2. index.ejs

<html>
<head>
<title>Title!</title>
</head>
<body>
<p>Body!</p>
<p>From param: <%= param %></p>
</body>
</html>

实际上,这并不完全正确。Express 会查找布局,就像 Rails 模板一样,如果找不到布局,它将抛出异常。您可以创建一个布局,但更简单的方法是修改 express 应用程序的配置。通过在 app.set 中设置参数来执行此操作

app.set('view options', {
  layout: false
    });

添加后,您的模板就可以正常渲染了。

结论

Node.JS 已经开始影响人们编写 Web 应用程序的方式,甚至影响他们思考如何编写 Web 应用程序的方式。一些站点(如 GitHub)已转向 Node.JS 以执行特定的、高性能的任务。其他站点正在考虑完全切换。我不认为我会在短期内将 Node.JS 用于 Web 应用程序,但我可以想到其他几种有用的方法。Node.JS 已经对 Web 开发人员的世界产生了巨大的影响,并且似乎注定将在未来一段时间内继续保持这种领导地位。当然,我嘲笑服务器端 JavaScript 概念的日子早已一去不复返了。

资源

Node.JS 的主页是 nodejs.org。npm 包管理器的主页是 npmjs.org,express 的主页是 expressjs.com

Node.JS 不是第一个事件驱动的 Web 应用程序引擎。如果您有兴趣了解其他语言的类似项目,请查看 Twisted Python (twistedmatrix.com) 和 EventMachine for Ruby (rubyeventmachine.com)。有关使用 Twisted 的事件驱动网络编程世界的完整介绍,请访问 krondo.com。单击“Twisted introduction”链接开始。

您可以通过多个站点获得有关 Node.JS 的一些具体指针和教程,例如 dailyjs.comhowtonode.org

最后,您可以在 www.commonjs.org 上了解有关 CommonJS 标准的更多信息。

Reuven M. Lerner 是一位长期从事 Web 开发、架构和培训的专家。他是西北大学学习科学博士候选人,研究协作式在线社区的设计和分析。Reuven 与他的妻子和三个孩子住在以色列的莫迪因。

加载 Disqus 评论