写给PHP开发者的Node.js学习指南

978-7-115-34498-4
作者: 【美】Daniel Howard
译者: 夏思雨
编辑: 陈冀康
分类: Node

图书目录:

详情

本书针对将PHP和Node.js功能结合到一起的主题,教授读者如何使用这两种强大工具,开发出商业级的Web应用。本书介绍了一些实用的技巧,将已有的PHP代码移植到Node,用Node这一开源框架实现功能。

图书摘要

版权信息

书名:写给PHP开发者的Node.js学习指南

ISBN:978-7-115-34498-4

本书由人民邮电出版社发行数字版。版权所有,侵权必究。

您购买的人民邮电出版社电子书仅供您个人使用,未经授权,不得以任何方式复制和传播本书内容。

我们愿意相信读者具有这样的良知和觉悟,与我们共同保护知识产权。

如果购买者有侵权行为,我们可能对该用户实施包括但不限于关闭该帐号等维权措施,并可能追究法律责任。

• 著    [美] Daniel Howard

  译    夏思雨

  责任编辑 陈冀康

• 人民邮电出版社出版发行  北京市丰台区成寿寺路11号

  邮编 100164  电子邮件 315@ptpress.com.cn

  网址 http://www.ptpress.com.cn

• 读者服务热线:(010)81055410

  反盗版热线:(010)81055315


Copyright© 2013 by O’Reilly Media, Inc.

Simplified Chinese Edition, jointly published by O’Reilly Media, Inc. and Posts & Telecom Press, 2013. Authorized translation of the English edition, 2013 O’Reilly Media, Inc., the owner of all rights to publish and sell the same.

All rights reserved including the rights of reproduction in whole or in part in any form. 本书中文简体版由O’Reilly Media, Inc.授权人民邮电出版社出版。未经出版者书面许可,对本书的任何部分不得以任何方式复制或抄袭。

版权所有,侵权必究。


Node.js是一套用来编写高性能网络服务器的JavaScript工具包。结合PHP和Node.js,我们可以概览整个Web服务器从起源到现在的历史过程以及其中的改变。

本书的目的是帮助PHP开发人员,借助已有的知识,快速学习和掌握Node.js的开发。全书共16章。全书从入门到深入,分别介绍了Node.js基础知识、Node.js架构、回调、HTTP Response、基本语法、变量、类、文件访问、MySQL数据库访问、JSON和XML、函数等内容。

本书可以作为Node.js的教程从头开始阅读,也可以作为参考查看如何用Node.js实现特定的PHP特性,或者作为将任何PHP应用转换为Node.js的实践教程。本书适合有一定基础的JavaScript程序员阅读,也适合对学习Node应用开发感兴趣的读者学习参考。


如果你是一个有经验的PHP开发人员,并且已经开始学习如何编写Node.js。在本书中,作者Daniel Howard介绍了PHP和Node.js两种语言惊人的相似之处,并介绍了如何将整个PHP web应用转换为Node.js。通过对比特定的PHP特性和Node.js,你也会看到如何重构和改进现有的PHP4或者PHP5源代码。

最后,你可以提供两个功能完备的代码库。你可以使用本书作为参考,同步更新两个代码库。

Daniel Howard,Ricoh Americas Corporation高级原理工程师,有超过20年的软件开发经验。他还是ajaximrpg开源项目的创始人和维护者以及Ajax IM开源项目的维护人。这两个项目都来自于本书。

写给PHP开发人员的Node.js 是一本非常好的结合了实例和细节讲解的书。Daniel带你一步步地学习如何基于现有的PHP知识创建真实可用的Node.js项目。从语法到数据库,本书可以做为初学者和专业人员都可使用的参考书籍。

——Joshua Gross

自由职业的web设计师和开发人员


为什么要写这本书呢?

就互联网语言来说,PHP产生于1995年,是一门很古老的语言;而Node.js产生于2009年,是一门非常新的语言。结合PHP和Node.js你可以鸟瞰整个Web服务器从起源到现在的历史过程以及其中的改变。但是更重要的是,这些内容告诉了我们哪些没有改变——没有改变的那些,是整个行业公认的最佳实践——以及一点点未来可能的样子。

PHP与Node.js最大的不同在于,PHP是一门阻塞型语言,依赖于API并且在执行结束前并不返回任何结果,而Node.js是一门非阻塞语言,依赖于执行结束时使用事件和回调机制的API。除了这一点外,PHP和Node.js惊人地相似。PHP和Node.js,都类似于C语言,使用花括号({})作为代码段的标记,有function关键字,用于同样的目的以及语法相同。如果Node.js验证了阻塞型API已经是过去时了,那么它同样也验证了C语言在过去、现在、未来的一些演化。回调机制可能是一个演化的进步,但是语法已成定式。

但是,“哦,听起来好像没那么有趣”,为什么要出版这本书呢?

相当数量的网站托管服务都提供了对PHP的支持。如果你开发了一个Web应用,希望交给别人来运行,如果是由PHP编写的话几乎可以在任意地方安装。他们可以购买每个月10美金的Web服务来安装你的PHP程序。

但是Node.js并没有这么大范围的支持。事实上我并不知道有哪个Web托管服务是支持Node.js的。但是我了解的是很多开发人员都对Node有兴趣并尝试用Node进行开发。使用Node.js开发,Web程序代码对开发人员来说变得有趣了。如果你开发了Web程序,并希望其他开发人员可以改进和重用,他们可以从GitHub或者其他源代码服务获得你的Node.js Web程序。

在完美的世界中,也许你可以同时满足这两种人的需求。

但是我们的世界并不完美,不过你可以将PHP代码移植到Node.js,同时用这两种不同的语言开发两个代码库,来满足不同的需求。

本书的使命——当我写下“使命”这个词的时候,我的意思是“真的,真的,真的希望你这么做”的使命——是说服你将自己的PHP代码转换为Node.js代码。我希望你不仅仅是阅读这本书,而是真的坐在电脑前,找到那些你最厌倦的PHP4代码,参考本书,将其转换为Node.js。我希望你可以亲自看看PHP和Node.js并没那么大的不同,你并不需要抛弃PHP代码用Node.js从头重写。我希望你可以看到你并不用禁锢在PHP代码中,成为历史的囚徒。

你会看到将PHP代码转换为Node.js并不仅仅只是使用Node.js,同样也是对你的PHP代码的改进。本书中一个很重要的步骤就是重构并改进你的PHP代码使之更便于转换为Node.js。本书的目的并不在于创建一个新的Node.js代码库,这关乎两个代码库:PHP和Node.js。完成从PHP到Node.js代码库的转换会使你成为更好的PHP开发人员。

如果你是一个PHP开发人员,这本书再适合你不过了。你可以通过现有的PHP知识学习如何开发Node.js。你知道在PHP中如何完成的功能,比如读取文本文件,在接下来的章节中你会看到用Node.js是如何完成同样的工作。与其他Node.js出版物不一样,本书没有介绍通用的文件操作,而着重于将Node与PHP进行对比。所以你可以看到你所了解的语言和你所学习的语言中的具体细节。你甚至可以注意到一些之前没有注意到的PHP的死角,因为这些死角中的一部分是Node.js的核心概念。

如果你已经是一个Node.js开发人员,本书提供了一个很好的学习PHP的机会。如果PHP开发人员可以通过对照学习Node.js和PHP的方式了解Node.js如何工作,那么Node.js开发人员也可以通过同样的方式学习PHP。更重要的一点,通过Node.js与另一种特定语言的对比,比如PHP,你可以更清楚Node.js与PHP到底有多类似。

对比两种语言,或者学习如何从一种语言移植到另一种语言,是一个很好的可以成为这两门语言的专家的方法。其他一些只涉及一种语言的书籍,更像是一种手把手教程或者百科全书。他们会告诉你,“这个是这样”,“那个是那样”,只讲解一些抽象概念。其他书无法像本书一样根据两种语言的对比给出有力的解释。

除了更有效率以外,本书更具有趣味性,更关注在值得注意的话题上。在一般的Node.js编程书中,花费大部分时间讲解语句的含义以及为什么Node.js语句以分号结尾。这很无聊。但是如果一本书在没有任何参考(比如PHP语言)的情况下解释如何用Node.js编程,你无从进行对比。而在本书中,我假设你已经了解PHP程序和基本语法,所说的只有一点,Node.js也是这样的。在本书中,我假定读者有特定的背景——PHP开发——而不需要为熟悉Python或者Microsoft Office的读者介绍更多一般意义上的内容。

虽然我努力劝说开发人员从PHP代码转向Node.js代码,但我并没有说PHP代码不好。事实上,我认为PHP是一门很强大的语言。我的意图并不是说服你将PHP代码移植到Node.js然后完全抛弃PHP。我鼓励的是,你在移植过程中保留PHP原始代码并不断改进,同时成为一个熟练的Node.js开发人员。PHP和Node.js同样重要。

当最开始准备写这本书的时候,我做了一个重要的决定:我决定关注在现实实践中存在的PHP代码。PHP 5是PHP现在的最新版本,但是仍然还有很多PHP 4的代码。本书并没有按照惯例将PHP 4代码转换为PHP 5,然后再根据本书将PHP 5代码转换为Node.js。尽管PHP 5中对PHP 4的支持越来越少,但本书毅然选择一条艰难的路,告诉你如何将PHP 4代码改进并移植到Node.js,而不需要PHP 5的特性。尽管本书也会介绍如何将PHP5代码转换到Node.js,但我向你保证使用本书也可以使你的PHP 4代码转换为Node.js。

做完这个决定之后,很快我又做了另一个与本书相关的决定:我要介绍一个转换系统,可以保证PHP代码和Node.js代码的同步来完成转换过程。在转换结束的时候,PHP和Node.js的代码库都是功能完善的、可扩展的。新的特性和bug修复可以在两个代码库上同步进行。这个决定又选择了一条艰难的路线。简单做法是,选择采用“转换和丢弃”方式转换过程,PHP代码不会同步更新,最终可能无法工作,开发人员唯一的选择就是继续使用Node.js代码库。这可能会使本书的内容缩减一些,但是这是一种低级做法——对于作者来说,可以少写点内容更容易一些,但是对于读者来说,本书的作用就没有那么大了。

这两个决定,支持PHP 4和支持两个同步的PHP和Node.js代码库。作为最终产品,本书内容比一般书籍多很多,但是也更有实际用途。这本书不是一次性阅读之后放在书架上就不看了的书,它可以作为参考随时查阅与PHP或者Node.js相关的内容。

到现在你也许了解了本书的一部分任务,以及为什么值得这么做。但是你可能仍然抱有怀疑态度。

考虑以下PHP代码,来自于一个真实的PHP Web应用,实现的功能是即时通信—聊天:

在Node.js中等价的代码:

当然,语法有些不同。比如拼接字符串,PHP使用点操作符(.),而JavaScript使用加操作符(+)。PHP使用array()来初始化数组,而JavaScript使用方括号([])。这些都是不同的。

但是老天保佑,相似之处还是比较多的。这并不是伪码,它使用数组,访问MySQL数据库,使用JSON,并且输出。

将PHP源代码转换为相似的Node.js的可能性,以及为O'Reilly Media编写本书,都来源于我自己的经历,用Node.js实现了我的开源工程。

我是Daniel Howard,ajaximrpg的创始人和维护者。ajaximrpg是一个卓越的基于浏览器的即时通信和聊天系统,特别用于桌面角色扮演游戏,比如网游Dungeons & Dragons。除了角色扮演的特性之外就是一个一般意义上的客户端。Ajaximrpg是一个完全开源的项目,通过Source Forge支持一系列服务,比如Twitter feed、Google Group和在线演示。

Ajaximrpg最开始使用PHP 4编写,完全没有预计到某天会使用Node.js JavaScript实现。但是,之后是PHP 5,现在是Node.js。

从2012年1月起,我用了仅一周的时间验证使用Node.js可以使我的客户端JavaScript代码检测到运行Node.js的服务器端程序的安装情况。接下来一个月内,我大概修改了几千行代码来完成用户登录以及与其他人通信的功能。这启发了我,可能有一些基本的工作原则,可以写一本书来解释如何使用这些基本原则将PHP代码转换为Node.js,读者可以快速而准确地转换他们的PHP源代码,而不必再走一次我走过的弯路。

所以我放下了快要完成的Node.js版本的实现并立即开始写这本你现在正捧着(或者在电脑屏幕上阅读)的书。

本书一共12章,从入门到高级。

第1章 Node.js基本介绍

本章介绍了如何安装Node.js以及使用Node可执行文件、node和npm。还介绍了如何安装Eclipse PDT并配置其支持PHP到Node.js的转换。

第2章 简单的Node.js架构

本章介绍了一个简单的Node.js架构,可以将独立的PHP页面转换为Node.js文件,在进行操作时,比如访问URL,通过Node.js Web服务器使用转换后的Node.js文件。

第3章 简单回调

本章介绍如何重构阻塞型的PHP源代码使其可以很容易地转换为非阻塞型的使用回调机制的Node.js源代码。本章中介绍了线性的概念,作为一种简便的方式用于分析和改进PHP代码以便于在转换为Node.js的过程将其放在Node.js的回调中。

第4章 高级回调

本章介绍了一种更复杂但是更通用的方式来重构PHP 4代码,模拟匿名函数、函数变量以及闭包。而对于PHP 5源代码来说,本章解释了如何使用PHP5的特性来实现匿名函数、函数变量和闭包。

第5章 HTTP Response

本章介绍了如何转换PHP输出,比如将print和echo关键字转换为Node.js的HTTP response。

第6章 语法

本章介绍了如何转换PHP语法,比如用Node.js方式拼接两个字符串。

第7章 变量

本章介绍了如何将单个和数组变量,还有一些公共的操作,比如添加或者删除数组元素,转换为Node.js;还介绍了如何将PHP类型转换为Node.js类型。

第8章 类

本章通过一步一步地演示转换过程,介绍了一种用Node.js实现PHP的类和类继承的方式。

第9章 文件访问

本章介绍了PHP和Node.js利用API所做的全部的文件读写操作,以及如何将PHP文件处理转换为等价的Node.js操作。

第10章  MySQL数据库访问

本章介绍了数据库,特别是MySQL数据库,在Web应用中的所有使用方式。根据转换过程一步步地将PHP MySQL API访问数据库的过程转换为使用 Node.js npm node-mysql包。

第11章  文本、JSON和XML

本章介绍了三种数据类型:纯文本、JSON和XML。介绍了如何将使用PHP JSON或者XML API的PHP源代码转换为使用类似Node.js npm包实现。

第12章 各种各样的函数

本章介绍了很多PHP API的Node.js实现。这些Node.js实现可以加快转换过程,并提供了有效的方式对比PHP和Node.js。

那么,现在就开始使用Node.js吧。

本书目标在于从现有的PHP源代码开发新的Node.js程序。PHP和Node.js有很多相似之处,当然,也有很多关键的不同之处。利用这些相似性和差异,你可以借助现有的PHP开发经验学习Node.js,并且最终创建一个Node.js Web程序直接替换你现有的PHP程序。

本书假定你是一个开发人员,了解开发的基本知识,比如知道编码的工作流是创建然后实现一个设计。同时假定你熟悉什么是类、函数、循环体、Web开发,包括基本的Web应用中的Web浏览器和服务器的交互。

更进一步,本书还假定你拥有PHP编程经验。如果没有PHP背景,也可以使用你熟悉的其他编程语言(比如Python、Ruby或者C),在阅读本书过程中,找到PHP、Node.js和你熟悉的编程语言之间的交集,这要求对PHP和Node.js都有很好的理解,虽然并不容易做到这一点,但是你可以尽力尝试。

这本书可以作为Node.js的教程从头开始阅读,也可以作为参考查看如何用Node.js实现特定的PHP特性,或者作为任意PHP应用转换为Node.js的手把手教程。本书可以用于以上几种目的。

不管你如何阅读本书,作为本书作者,我真诚地希望它可以回答你关于PHP和Node.js的所有疑问。

以下是本书中使用的印刷惯例:

文本

菜单标题,菜单选项,菜单按钮,键盘快捷键(比如Alt和Ctrl)。

斜体

新的术语,URL,邮件地址,文件名,文件扩展名,路径,目录和Unix用法。

等宽字体

命令,选项,switch,变量,属性,key,函数,类型,类,命名空间,方法,模块,参数,值,对象,事件,事件处理,XML标记,HTML标记,宏,文件内容或者命令行输出。

等宽粗体

显示命令或者其他应该由用户输入的文本。

等宽斜体

显示应该由用户提供的内容替换的文本。

 

提示

这个图标表示提示,建议或者一般的记录。

 

警告

这个图标表示一个警告或者提醒。

本书用于帮助你完成工作。一般来说,可以在程序或者文档中使用本书的代码,并不需要联系我们询问关于版权的问题,除非你大量复制代码。比如,使用本书中的几段代码编写一段程序并不需要我们的许可,但是如果使用O'Reilly书中的代码买卖或者制成CD,则需要我们许可才可以。将本书中的大量代码作为产品文档也需要授权。

我们非常感激你在引用时标明出处,但这并不强制。引用包括标题、作者、出版商和ISBN。比如:“Learning Node by Shelley Powers (O'Reilly). Copyright 2012 Shelley Powers, 978-1-449-32307-3。”

如果你觉得你的使用不在上述范围之内,请联系我们permissions@oreilly.com。

Safari Books Online(www.safaribooksonline.com)是一个即时的电子数据库,传递全世界在技术和商业领域著名的作者的书籍和视频。

技术专家、软件开发人员、网站设计师、商业和创意专家都使用Safari Books Online作为他们搜索、解决问题、学习和培训的主要资源。

Safari Books Online为组织、政府代理和个人提供了不同的产品搭配组合和定价。购买者可以访问数以千计的书籍、培训视频,从出版商的数据库访问还未发布的手稿,比如O'Reilly Media、Prentice Hall Professional、Addison-Wesley Professional、MicrosoftPress、Sams、Que、Peachpit Press、Focal Press、Cisco Press、John Wiley & Sons、Syngress、Morgan Kaufmann、IBM Redbooks、Packt、Adobe Press、FT Press、Apress、Manning、New Riders、McGraw-Hill、 Jones & Bartlett、 Course Technology和其他很多。更多信息请访问我们的网站。

对于本书的评论或问题请联系出版商:

美国:

O'Reilly Media,Inc.

1005 Gravenstein Highway North

Sebastopol,CA 95472

中国:

北京市西城区西直门南大街2号成铭大厦C座807室(100035)

奥莱利技术咨询(北京)有限公司

我们为本书制作了一个Web页面,那里列出了勘误、示例以及其他的额外信息。你可以访问这个页面:http://shop.oreilly.com/product/0636920026013.do

技术问题或评论本书,请发送邮件至:

bookquestions@oreilly.com

获取有关图书的更多信息、课程会议和新闻,请查看我们的网站:

http://www.oreilly.com.cn

http://www.oreilly.com

本书是我和其他很多人很多个月的心血。

我要感谢O'Reilly Media的编辑们,特别是Simon St.Laurent 和Maghan Blanchette,感谢你们的鼓励和反馈。

我要感谢本书的技术编辑——Neha Utkur,感谢她在本书编写过程中热心地提出各种反馈,使本书变得更好。

最后,我要感谢Shelley Powers百忙之中抽出时间来对本书进行技术方面的检视。


我们假定你有一个PHP代码库需要移植到Node.js。在可预见的未来你需要为用户提供PHP和Node.js两个代码库,这意味着你需要同步更新和改进这两个代码库。但是你并不了解Node.js,也没有做过任何有关Node.js的开发。那么该从哪里下手呢?

首先需要下载支持你的平台的Node.js,可能是Linux或者Windows版本(是的!Node.js还提供了Windows的版本!)。因为每个版本的安装方法和安装工具都有所不同,所以本书不在安装当前版本上花费时间。如果需要安装说明,可以浏览在线文档;如果还是失败了,可以通过Google或者其他搜索引擎查找其他人发布的相同问题的网页或者论坛帖子,然后找到解决方案。

安装完成后,你会发现运行Node.js非常简单,它主要由两部分组成:node和npm命令。

Node命令使用非常简单。尽管它有很多参数,但是一般情况下你只会给node传递一个参数,就是Node.js源文件的文件名。例如:

node命令会在源文件(本例中是hello.js)中解析Node.js代码,执行代码,完成后退出回到shell或者命令行。

注意到hello.js文件扩展名为.js。.js扩展名表示JavaScript。好处在于,以.js为扩展名的文件既可以是客户端JavaScript,也可以是服务器端Node.js代码。不过,尽管这两者都是用JavaScript,但是他们之间并没有其他共同之处了。客户端JavaScript代码需要服务于浏览器,而服务器端Node.js代码需要可执行node命令或者可以访问通过node命令运行的main Node.js代码。这导致了不必要的误解。

在一些Node.js项目中,客户端的JavaScript文件放在同一个文件夹中,比如client文件夹,而Node.js文件放在另一个文件夹,比如server。通过文件夹方案来分离客户端JavaScript文件和Node.js文件仍然会存在一些问题,因为很多代码编辑器在标题栏或者标签中只显示文件名而不是全路径。

所以,在我的项目中,对Node.js文件采用了.njs作为扩展名,对客户端JavaScript文件则保留.js作为扩展名。让我说得更清楚一点,.njs文件名并不是标准扩展名,至少,现在还不是(也许永远都不会)。我还用Google搜索了一下,一般做法是Node.js的扩展名为.js。为了避免一直混淆客户端和服务器端的JavaScript,我使用了.njs作为Node.js代码的扩展名,在PHP到Node.js移植过程中,我也建议这么做。

所以,我使用hello.njs代替之前的hello.js:

本书其余部分都会使用.njs作为Node.js文件扩展名。

简单的hello.njs文件内容为:

如果你运行node hello.js,会在控制台输出“Hello world!”然后退出。

为了运行一个Web服务器,可以使用hellosvr.njs源文件:

如果运行node hellosvr.njs,终端命令行会挂起。服务器必须一直运行,才能接受来自页面的请求并做出响应。

如果打开Firefox或者Chrome,在地址栏输入http://127.0.0.1:1337,就可以看到一个简单的页面,显示“Hello world!”。事实上,访问http://127.0.0.1:1337/index.html,或者http://127.0.0.1/abc,甚至是http://127.0.0.1/abc/def/ghi,都会看到同一个显示“Hello world!”的页面。因为我们现在的服务器对所有页面请求都做出同样的响应。

这段代码中重要的是第一行,使用Node.js 全局函数require()。Require()方法用于加载一个你想要使用的的Node.js模块。模块是指数据和方法的一个集合,提供了某个特定方面的功能。在本例中,Node.js的http模块用于提供HTTP服务器功能。

Node有很多内建的模块:http、https、fs、path、crypo、url、net、dgram、dns、tls和child_process。这些内建模块的功能随着版本变化也有不同。

从设计角度来讲,一个模块代表了一个命名空间(namespace)。命名空间是指加在数据或者函数引用之前的额外描述。比如,http是createServer()方法的命名空间。在Node.js中,命名空间就是一个对象。当http模块被加载时,require()方法返回一个对象并赋值给http变量,变量名并不一定非要是http,也可以是“xyzzybub”,如果是这样,服务器创建的方法就是xyzzybub.createServer()。

为什么需要有命名空间呢?为什么不直接把所有数据和方法都作为全局变量呢?

Node.js期望新的模块代表新的功能,比如访问MySQL数据库,应该由其他人开发然后在Node.js安装完成后集成在node命令中。而在这些模块中的数据和方法名可能各种各样,开发人员很可能不小心使用了别的开发人员在其他模块中使用的函数名。但是由于模块都存在于一个命名空间中,可以由命名空间来区分同名的函数。实际上,在之前的编程语言比如C++和JAVA的基础上,Node.js的一个重要改进就是允许模块的使用者自定义模块的命名空间,用户自己会将模块赋值给自定义的变量,比如http或者xyzzybub。

这些具有新功能的新模块可以看做是包(package)。包是指一个可以添加到node命令行但是不是默认集成到node的模块。模块和包的界线并不明显,可以理解为只是专业术语上的变化。

Npm(node package manager)命令可以为node添加新的package。

要安装一个package,首先使用Google或者其他搜索引擎找到你想要安装的那个npm package,大部分时候,都在GitHub上。另一种不同于搜索引擎的方式是使用npm search命令来查找想要安装的package。

设想一下我们需要一个Web服务器通过硬盘的文件提供静态页面,而不总是返回“Hello world!”。为了找到一个Node.js静态文件服务器模块,一个好的搜索关键字是“nodejs static file server”。或者直接使用“npm search static”、“npm seach file”或者“npm search server”,会列出npm package名字或者描述中包含“static”、“file”或者“server”的package。不管使用哪一种方法,你都会找到Alexis Sellier,a.k.a cloudhead,创建的非常流行的静态文件服务器模块,地址是:https://github. com/cloudhead/node-static。

可以用以下命令安装package(附加选项,比如-g或者--global,可以用于配置包的安装方式):

npm命令会获取package,然后成功安装(希望是这样)。以下是安装成功的一些输出:

GET表示使用HTTP GET方法来尝试获取package。200表示HTTP GET方法返回“HTTP status 200 OK”,意味着获取文件成功。

现在有很多npm package,但是只有一部分非常流行,比如express、node-static、connect、sockets.io、underscore、async和optimist。

使用以下httpsvr.njs文件,来实现一个Web服务器提供静态页面:

这就是Node.js开发的基本过程。需要编辑器来创建或者修改包含Node.js代码的.njs文件。当需要新的功能而node中没有时,npm命令可以下载并安装包含所需功能的npm 包。node命令用于运行.njs文件来执行Node.js代码,然后可以测试和使用该Web应用。

现在,我们已经看到过3个Node.js服务器了:hello.njs、hellosvr.njs还有httpsvr.njs。这些源文件都很简单,不必关心它们是怎么创建的。可以用任何文本编辑器来创建这些文件。如果什么地方出错了,很容易直接编辑修改。

我们可以放心地假设你已经有了一个复杂的Web应用,有很多文件,成千上万行需要移植的PHP代码。移植会是一个冗长的按部就班的过程。

第一个步骤需要创建一个Node.js的样板文件用于保存新的Node.js代码,具体在第2章中描述。这个样板文件会被修改,用于响应用户发起的特定URL请求。这种转换是为了使Node.js服务器对客户端的响应方式与PHP服务器相同。为了做到这一点,需要修改Node.js样本文件来处理每个HTTP请求,并将请求转发到特定的Node.js代码段进行处理,这些Node.js代码段是之后用于在Node.js中实现特定PHP页面的功能。

第二个步骤是重构PHP代码,主要在第3章和第4章中介绍,使PHP更容易移植到Node.js,换句话说,使PHP代码对Node.js更友好。转换的过程可能有些让人吃惊,它不仅仅是维持现有的PHP代码状态不变,复制到一个Node.js文件,然后一行一行地将PHP代码转换为Node.js。PHP和Node.js代码都会同步改进并且添加新的特性,所以鉴于两种语言的不同工作方式,PHP和Node.js都需要保留一些各自的特性来克服这些差异,这是有意义的。PHP代码需要做一些重构,并且为了之后需要创建的Node.js代码的功能做出一些牺牲。在移植结束的时候,两个代码库看起来会非常相似,都使用一种混合元语言编写,PHP的一系列惯例和算法都很容易移植到Node.js。这种元语言会使两个代码库看起来有点奇怪,但是功能都是完备的,并且随着时间推移,维护和改进这两个代码库的开发人员会越来越熟悉和理解这种语言。即使你决定最终扔掉所有的PHP代码只使用Node.js,也最好先从重构PHP代码开始,将PHP和Node.js代码都移植到那种奇怪的元编程语言,然后扔掉PHP,最后再将混合语言的Node.js重构为纯Node.js。不管你的目标是什么,重构PHP代码是将任何PHP移植到Node.js过程的关键步骤。

第三步就是将PHP页面从PHP文件中复制到Node.js文件中。当然,Node.js服务器一定会挂掉,正在运行的node命令会立即退出,输出错误的堆栈信息。

第四步就是转换并修改新添加的Node.js文件,在剩下的章节中会介绍,然后就成为可以工作的Node.js代码了。最开始,Node.js服务器无法工作,并且立即退出、打印堆栈信息。堆栈追踪信息可以指出错误发生的地方,可能是由于部分PHP代码没有完全转换为Node.js代码或者转换错误。分析清楚问题之后,可以对整个Node.js文件使用后面章节介绍的转换技术。比如,第7章介绍了使用array()初始化数组的方式将PHP转换为Node.js对象的初始化方法,使用花括号({})。当Node.js服务器可以重新运行之后,可能可以运行,但是很可能又继续出错退出。最终,Node.js代码完善,服务器可以运行。

令人惊讶的是有相当多的PHP代码存在于Node.js代码文件中,但不会导致Node.js服务器运行出错退出。当你对转换过程更熟悉之后,你会发现PHP和Node.js有很多相似,没转换的PHP代码会被node命令解析,并在node运行中可以接收HTTP请求,直到真的需要执行一些PHP代码时才会失败。

一旦Node.js代码没有问题,服务器可以运行,就可以开始用客户端进行测试了。客户端一般都是浏览器,比如Firefox、Google Chrome。一般来说,使用客户端进行测试时,Node.js会在某个点上出错退出,然后你需要分析堆栈追踪信息,并做出修正。一段时间之后,你会形成一系列特定的测试用例,可以用于客户端执行以发现未定位的转换问题,或者通过这些测试确保服务器工作正常。

有时,也可以用可视化的比对工具来比较PHP和Node.js代码。通过在源PHP代码中一步步地查看,可以更容易地定位新的Node.js代码中的问题。这可以提醒你使用一些尚未使用但是需要的转换技术,也可以追踪转换过程并很好地控制该过程。

剩下的PHP到Node.js的转换过程就是将之前的步骤重复执行很多次,直到PHP代码都转换为Node.js,并且Node.js代码可以稳定地工作。根据PHP代码库的大小,转换过程可能耗时几个月,但是如果你下定决心,转换过程可以很快结束。

在转换过程中,会看到很多的堆栈信息,非常之多。以下示例显示了运行httpsvr.njs时,node-static npm包未安装导致的错误以及生成的堆栈信息:

堆栈信息的最上面一行显示了抛出异常的代码所在。这并不是产生错误的代码,而是创建并抛出error对象的代码。

这行之后,显示的是Error对象内部的错误信息。这个错误信息告诉了我们没有找到node-static模块。

其余的部分称为“调用栈”,是一系列由“at”开头的链式函数调用,直到到达抛出异常的代码。调用栈按照从内到外的顺序排列。在本例中,Function._resolveFilename()函数是调用栈的最顶端,表示它是最内层调用的函数,直接包含了抛出异常的代码。._resolveFilename()函数被Function._load()函数调用,该函数又被Module.require()函数调用,Module.require()又被require()函数函数,再上层就是Object.<anonymous>()函数,依次类推。

在调用栈中每个函数的调用之后,你都会看到包含该函数的源文件名,最后一行执行的代码(可能是调用函数之前的一行或者抛出异常的那行),以及该行代码中最后的执行位置。在上个例子中,可以看到涉及的两个文件是:module.js和httpsvr.njs。

module.js文件存在于node命令中,我们并不认为这是属于我们自己的代码文件,而httpsvr.njs则是我们自己的源代码。尽管调用栈中只显示了一次httpsvr.njs,但是可以认为引起错误的是我们自己的代码。一般来说,我们认为Node.js本身,其内建的模块,和其他安装好的npm模块都是安全可靠的。即使它们不是,我们也必须假定它们是正常工作的,除非我们证明排除了所有由我们的代码产生错误的可能。但是,即使我们发现了错误是由其他原因导致的,我们能控制的也只有我们自己的源代码。解决方式也应该是在我们的代码中找到变通方案而不是通过漫长而缓慢的过程找到其他开发人员来修改他们的代码。所以,结果是,不管最终的错误是什么,首先需要关注的都是httpsvr.njs文件。

我们需要关注的调用栈中的部分是:

函数调用在httpsvr.njs的第2行第14个位置。Httpsvr.njs文件为:

通过在源代码中交叉引用调用,用于加载的node-static模块的require()函数就是错误发生的地方。这和错误信息“Cannot find module ‘node-static’”一致。

如果我们再看看调用栈,可以看到栈上方的Function._load()函数和._resolveFilename()函数。看看这两个函数名,我们可以猜测Node.js环境加载该模块出错是因为找不到与模块相关的文件。所以可以做出假设找不到模块文件(可能是npm包)是因为模块没有安装。再一次,这验证了错误信息“Cannot find module ‘node-static’”。

Object.<anonymous>函数暗示我们,require()函数的调用是在全局范围内,而不是httpsvr.njs用户自定义范围。但是事实并不一定都是这样。一个匿名对象也有可能在用户自定义的函数内生成。但是,继续看调用栈,在Object.<anonymous>函数调用之后,我们看到调用者是module.js中的Module._compile函数。所以,require()函数是全局范围内的调用。

综合以上所有信息,一个解决方案是安装node-static npm package:

诚然,不需要每次在看Node.js调用栈时都做这些分析。但是你可能会看到很多很多的调用栈,你应该知道如何进行分析——特别是因为找到并修复错误占据了转换过程95%的时间。

总结一下,分析一个调用栈信息的过程是:找到错误,查看错误信息(如果有的话),做出假设并关注在自己代码中的特定函数调用,阅读代码并找出错误可能产生的位置,查看调用栈是否包含更多错误的可能信息,根据堆栈信息理解服务器如何执行到该调用函数。

学会如何分析堆栈追踪信息是PHP到Node.js成功转换的一个重要技能。堆栈追踪是查找代码错误的一个诊断工具,就像医生用X光来检查病人一样。从某种观点来看,PHP到Node.js的转换可以类似于一个复杂的外科手术。你要对PHP和Node.js代码进行手术。像手术过程一样,转换需要技巧和耐性,但是一个好的环境也有很大帮助。就像手术室中使用X光一样,堆栈追踪就是转换过程中开发环境中的工具。下一步,我们会讨论集成开发环境,为转换过程提供类似“手术室”一样的环境。

很快你可能就需要面对几十个PHP文件,成千上万行的PHP代码,紧接着,几十个Node.js文件和成千上万行的Node.js代码,一个简单的文本编辑器无法有效地追踪所有的内容完成转换。对于尝试一个简单的例子来学习如何使用Node.js编程来说,文本编辑器足够了,但是在面对大量的PHP和Node.js代码时,你需要更有效的工具。

在开发PHP或者Node.js本身时,你可能会选择一个单语言支持的集成开发环境(IDE)直接使用。Eclipse PDT(PHP Development Tools)是一个非常流行的PHP IDE,用Java编写,由Eclipse基金创建。其他的还有Zend Studio、PHPEdit和Dreamweaver。对Node.js来说,选择就比较少了,并且对其适用范围和有效性持怀疑态度。在写本书的这个时候,我知道的有Komodo Edit、nide和Cloud9。

然而,你的目标是将PHP代码移植到Node.js,同步地改进两个代码库并添加新的特性。为了有效地完成这一过程,我推荐使用Eclipse PDT,需要做一些修改来支持Node.js。还需要知道的是如何在转换过程中更容易地对比PHP和Node.js代码。

现在,在我开始介绍如何为PHP到Node.js转换过程设置Eclipse PDT之前,我需要为那些拒绝使用类似工具坚持使用文本编辑器的开发人员做出声明。他们说,“我只使用vi”。如果你也是这么想的,那么你可以跳过本章其余的部分然后用任何你觉得对的方法设置你的转换环境。我会在这里介绍如何安装以及配置Eclipse PDT,只是因为对我来说它是一个非常重要的工具,帮助我将PHP代码移植到Node.js,对其他开发人员来说这也会是一个非常有用的工具。

要安装Eclipse PDT,首先下载Java。所有Eclipse IDE都是Java开发的,需要Java运行环境,当然包括Eclipse PDT。我偏向于安装Java JDK而不是JRE。在编写本书的这个时间,我使用jdk-6u29-windows-i586.exe。

下一步,浏览http://eclipse.org/pdt/downloads。考虑使用Zend Server Community Edition(CE)安装,这包含了Eclipse PDT、Zend Server HTTP 服务器、内建PHP调试支持,还有MySQL数据库。我假定你的PHP Web应用使用MySQL数据库,或者至少可以支持MySQL数据库。

在写书的时候,Eclipse PDT下载页面上有下载PDT和Zend Server Community Edition的链接。如果链接不存在了或者你有其他的Web服务器,可以根据你的操作系统下载合适的Eclipse PDT。然后可以跳过以下几段,一直到讲解安装和配置Eclipse PDT的段落。按照链接下载Eclipse PDT for Zend Server CE。现在,我用的是zend-eclipse-php-helios-win32-x86.zip。解压但是暂时不要运行Eclipse PDT。

在同一个页面上,下载Zend Server CE。我使用的是ZendServer-CE-php-5.3.8-5.5.0- Windows_x86.exe。

安装Zend Server CE。简要来说,一直选择默认的选项直到Setup Type 页面。选择该页面的Custom按钮而不是Typical,然后点击下一步。在Custom Setup页面勾选“MySQL”Server。然后完成安装。

到这里,Zend Server CE会显示一个浏览器页面来进行配置。我们这种情况并不需要对服务器本身做任何特殊配置。

安装MySQL 数据库服务器,并配置其作为Zend Server CE安装包的一部分。默认情况下MySQL数据库服务器root密码为空字符串(也就是“”)。

运行Eclipse PDT。Zend Server CE是基于Apache2,有一个htdocs文件夹。当Eclipse PDT运行时,会查找并选择htdocs文件夹作为Eclipse PDT的工作空间文件夹。如果你使用Zend Server CE或者Apache之外的Web服务器,选择文档根目录作为Eclipse PDT的工作目录,部署到Web服务器的PHP文件就可以在适当的位置进行修改了。

这并不在本书范围之内,但是如果你愿意,可以在你现有的PHP代码库上尝试使用PHP debugger。

Eclipse PDT和你的Web服务器是你的转换开发环境的基础。现在,我们来做一些修改,学习如何使用Eclipse PDT有效地管理和实现转换过程。

Eclipse PDT本身已经支持JavaScript文件,而Node.js本身就是JavaScript,所以也可以支持Node.js。但是.njs并不是标准的文件扩展名,所以Eclipse PDT并不会认为.njs文件是Node.js文件。所以如果在Eclipse PDT里打开.njs文件(例如,httpsvr.njs),会显示成文本文件,没有像一般JavaScript文件(.js)的语法提示颜色,也没有代码补充提示。

为了使Eclipse PDT将.njs文件作为Node.js文件,在Eclipse PDT主菜单里打开Window菜单,选择Preferences菜单项;然后你可以看到Preferences对话框,包含两个窗格(图1-1)。在左侧的窗口,你会看到一个preferences的目录和子目录组成的树形层级结构。右边的窗口,你会看到一个按左侧窗口选定目录的可以浏览和编辑偏好选项的对话框。

图1-1  Eclipse PDT Preferences对话框

在左侧窗口中,打开General 文件结构,选择Content Types选项。在右侧窗口中,你会看到content type的列表。在右侧窗口中打开Text树形文件夹。在Text目录下,选择JavaScript源文件。当你选择了JavaScript源文件,你会看到一个列表框,有一个选项“*.js”,在“File associations”列表框中,还有一个Add按钮在窗口的右中位置。单击Add按钮,会弹出Add Content Type Association 对话框(图1-2)。你可以在“Content type”编辑表中输入“*.njs”。

图1-2 Eclipse PDT Add Content Type Association对话框

然后,在所有打开的对话框上单击OK按钮保存修改。

保存修改完成后,对保存为.njs的Node.js源文件,JavaScript语法高亮和代码补全功能会生效。

语法高亮对.njs文件生效后,你可以通过单词的错误颜色注意到一些简单的Node.js语法错误。对任何编程项目来说,肉眼检测都是很重要的一部分,尤其是PHP到Node.js的转换过程。另一个有用的肉眼检测技术是使用一个先进的可视化对比工具来比较PHP和Node.js代码,可以看到所有的东西,包括转换的质量和进度。

对比工具可以显示两个文件的差异。简单的基于文本的对比工具一般会将每个文件的每一行的差异作为单色的文本输出。这种对比工具对于分析PHP和Node.js的转换没有太大帮助。我们需要更高级一点的对比工具。文件需要并排显示而不是作为交替行文本;需要颜色对比,而不是单色;显示到字符级别的差异而不是只显示不同行的内容。

Eclipse PDT内建了一个不错的可视化对比工具。我们可以使用这个工具来对比一个.php文件和其对应的.njs文件。使用的时候选中.php和.njs两个文件,右键单击,选择Compare With 子菜单然后在该菜单内选择Each Other菜单项。图1-3是使用 Eclipse PDT对比工具对一个简单的.php文件和对应的.njs文件进行对比的截图。

图1-3 Eclipse PDT对比窗口

你并不需要详细了解这个特性是如何工作的,只需要看到PHP代码和Node.js对比相似度即可。

左边是rand.njs文件,右边是rand.php文件。文件的差别用灰色显示,匹配到相似的字符用白色显示。

注意到有些行几乎完全是白色,除了一个美元符号($)是灰色。PHP和Node.js在同样的地方使用同样的关键字function,并且函数名位置也相同。多年以来,这成为各种新的语言的共性,避免语法结构的变化,采用相似的语法,比如定义函数。同样,也注意到while语句也基本一致。

这对开发人员来说是有好处的,对比php和.njs文件会变得更容易。

Eclipse PDT自带的可视化对比特性很好但是并不绝对可靠。有些时候,在文件中移动代码则希望可视化对比工具可以找到更多匹配的内容,这就是说,该工具会在两个文件中显示更多的白色。在文件中拷贝一个函数的时间与代码的表现和功能无关,但是可能会使可视化对比工具更精确地进行代码匹配。在转换过程中值得花一些时间来尝试在每个文件中移动代码,看看会对对比结果产生什么影响。

在Eclipse PDT中,代码既可以在单独的窗口中编辑,也可以在对比窗口中。如果在独立的窗口中编辑并且保存,任何显示该文件的对比窗口都会重新加载该文件并且重新进行比对。可视化对比工具的普遍实现都是在独立窗口中编辑代码之后对比窗口重新加载。

一般来说,将两个文件中的代码保持相同的格式,使用相同的名字(比如函数和变量),包括重构一个或者两个文件中的代码,都有助于对比工具找到尽可能多的匹配代码。

为了保持PHP和Node.js代码同步改进和添加新的特性,你需要依赖于对比工具确保PHP和Node.js代码的正确性。慢慢地,开发人员就可以培养一种感觉知道匹配到什么程度是最好的。

当匹配度不够时,对比工具就会偏离其本来的功能,尝试将.php文件中的PHP代码和.njs文件中的Node.js代码进行对比,这并不意味着匹配。会有很多表示差异的灰色的高亮,无法匹配。经常尝试修改可以纠正这种问题。

当白色高亮太多时,意味着.njs文件中的PHP代码没有完全转换为Node.js代码。即使.njs文件可以解析并且运行,太多的白色高亮意味着需要更进一步的转换。经常对Node.js代码进行肉眼检测会发现还没完成的一些特定转换。一个很容易忘记的转换就是给PHP变量加上美元符号($),Node.js变量不需要美元符号。给PHP代码中加上美元符可以减少白色匹配高亮,使对比工具接近于正确的匹配度。

视觉上的对比,特别是使用对比工具,要比交互式的测试PHP和Node.js代码快很多。肉眼检查可以作为“通烟测试”(Smoke Test)来检测转换过程是否接近正确。自动化测试用例,不在本书讨论范围内,可以快速检测移植的正确性与否。

在本书中,你有机会将一大段特定的PHP代码转换为对应的Node.js代码元素。例如,PHP中使用array()函数创建数组,而Node.js中,一般使用文字符号,比如花括号,创建数组。在转换开始时,拷贝了整个.php文件的代码到.njs文件进行转换,.njs文件中肯定包含很多PHP array()函数,需要被替换成Node.js对象的符号。一个简单的处理办法是使用Eclipse PDT的Find/Replace功能来进行“array(”的全局替换(全部替换为左半边花括号)。如图1-4所示。

图1-4 Eclipse PDT Find/Replace对话框

这个对话框的使用非常简单。

本书使用文本速记来代替每次说明Find/Replace对话框时的截图。对上图中的Find/Replace对话框来说,使用以下文本作为替换:

有两种不同的使用Find/Replace对话框方式。

一种方式称为无条件(blind)全局查找替换操作。如图1-4所示。

称之为“无条件”是因为该操作会在文件中一次完成所有查找和替换操作,不提示警告信息,不需要手动干预。如果所有的Find/Replace对话框都经过测试并验证为安全的,无条件的全局查找替换操作速度快并且准确。但是,如果结果出现错误,只有两个选择:撤销之前的操作或者进行一次新的替换纠正前一次的错误。

值得一提的是对于查找和替换纠正工作的第二个选择。有些时候,一个简单易于理解的替换操作可能正确地替换了298处代码,但有两处替换错误,要比一个复杂的查找替换操作正确替换了300处代码好很多。手动查找和修复一些边缘案例是值得的,并不是所有的事情都需要完全自动化。即使PHP到Node.js的转换过程是一个漫长的过程,你也不需要一遍一遍重复操作。本书介绍的并不是“持续转换”,而是将转换作为一次性事件。所以对于完成工作来说完全可以接受手动查找和修复少数边缘案例。

第二种使用Find/Replace对话框的方式是全局查找单步替换操作。首先,Find/Replace对话框用于查找第一个匹配到的内容所在。开发人员检查该处是否需要手动修改代码(可以通过点击该处代码而不需要关闭对话框)或者执行替换(点击Replace/Find按钮),或者跳过当前所在查找下一处匹配(再次点击Find按钮)。以下是单步全局查找替换操作的简介:

Eclipse PDT中的Find/Replace也可以使用正则表达式。正则表达式是一种模式匹配技术:正则表达式用于描述一种搜索的模式,而不是准确的内容。每次找到匹配模式时,匹配上的内容都作为待替换区域。比如,如果数组正则表达式\((.*)\)匹配到了array(id=>‘name’),正则表达式中的(.*)则表示内容id=>‘name’。这段内容被称为匹配字段,或者有时候被称为匹配组。在Eclipse PDT Find/Replace对话框中,匹配字段由它周围的括号决定。为了使匹配字段成为替换字段,匹配字段会按照匹配到的顺序列举出来。美元符($)用于描述一个特定的匹配字段,后面的数字表示匹配字段的编号。例如,替换区域中的$1表示第一个匹配字段,在之前的例子中表示id=>‘name’内容。一般情况下都只有一个匹配字段,所以经常使用的都是$1,很少看到$2、$3或者更多的。

以下表示使用正则表达式的无条件查找替换操作:

正则表达式的使用不包含在PHP到Node.js的转换过程中,所以本书并不会介绍如何理解和编写正则表达式相关的基础知识。所以正则表达式都是作为查找替换操作中的一部分,可以拷贝到Eclipse PDT Find/Replace对话框中的对应区域,你不需要去理解或者修改这些正则表达式。如果你确实需要正则表达式相关的帮助或者需要理解正则表达式的规则和它们的工作原理,鼓励你去研究Eclispse PDT,使用Google或者其他搜索引擎,查找网页、博客或者论坛来回答你的问题。

大部分时候使用正则表达式的查找替换操作比逐字逐句的替换查找(比如只匹配到一个特定的字符串)更容易理解并且效率更高。一个正则表达式可以使匹配的内容更加灵活,并且通过匹配字段,将匹配到的内容作为Replace字段。一般来说,按字符查找替换一次只能匹配到代码的开始或结尾,代码元素的中间部分可能有变化。而正则表达式,中间部分可以匹配某种模式,一次的替换查找操作就可以匹配整个代码元素。当这种代码元素的替换可以在一次查找替换的操作中完成,发生错误的概率就降低很多。

截止到这里,本章介绍了如何建立PHP到Node.js转换环境的一系列操作和相关知识。第一件事是下载Node.js本身,熟悉自带的两个命令。之后,通过调试Node.js堆栈追踪信息来学习如何阅读堆栈信息以及根据该信息找到定位问题背后真实的深层次的原因。接下来,安装Eclipse PDT作为开发环境的基础,包括对配置的修改使其支持.njs文件,专注于PHP到Node.js的转换。最后,了解了转换过程中很重要的可视化对比工具以及查找替换操作。

一个好的开发环境对提高开发效率以及完成工作至关重要。很多时候,业余的开发人员会陷入在编程的过程中而忽略了开发环境的重要性。最开始的时候,在任何环境下都可以很快开始开发工作,因为这个时候代码量较少容易改进。当代码库逐渐增长,代码的复杂度逐步上升,开发的速度就会降下来了。一个低效率的开发环境在复杂度层面上对开发人员没有任何帮助,但是一个好的开发环境可以帮助开发人员简化必备的知识维持开发速度,直到项目结束。

在PHP到Node.js的转换过程中,我们假定已经有一个很大的PHP代码库。在转换结束的时候,期待代码库在规模上可能会翻倍:PHP代码会因为转换进行重构,但不会缩减,所以代码量会增长。当然,还会添加一个完整的Node.js代码库。原始的PHP代码库可能由很多人合作开发,但是转换过程中的耦合太多一般是由一个人完成主要的工作。所以,对原始的PHP代码一个基本的开发环境也许就足够了,但是想要移植到Node.js则需要一个更复杂的开发环境。

如果一个项目已经有了一套自己的开发环境,也许不会选择Eclipse PDT。Eclipse PDT只是一个可用于转换的可工作的典型开发环境。其他的开发环境如果支持本章之前提到的各种特性,也可以使用。总而言之,开发环境需要支持.php和.njs文件的语法高亮,对两个文件进行单次级别的可视化对比而不仅仅是代码行级别,以及支持正则表达式的查找替换操作。

现在,转换过程的所有准备工作已经就绪,我们可以开始创建.njs文件来保存新的Node.js代码。在下一章中,会介绍一个初始.njs文件的模板,之后的章节中,PHP代码会被重构,复制到Node.js文件中,然后转换为可运行的Node.js代码。


在之前的章节,我介绍了一个用于PHP到Node.js转换的开发环境,以及如何使用它进行转换。在本章,我们将开始使用这个开发环境并进行实际的转换。

在PHP中,一个PHP文件代表一个HTML页面。一个Web服务器,比如Apache,当请求一个PHP页面时,Web服务器会运行PHP。但是在Node.js里,Node.js的main文件代表了整个服务器。Node.js并不是运行在类似apache这样的Web服务器中,而是取代apache. 因此,我们需要一些启动代码来让Web服务器正常工作。

我们来看看上一个章中作为例子的httpsvr.njs,下面是httpsvr.njs中的Node.js代码:

它是如何工作的呢?

如同在前面章节中提到的,require()函数可以加载一个模块并使用该模块。前两行代码分别展示了加载一个内建模块和外部模块:

如果你按照上一章的内容安装了Node.js并且实现了所有的例子,那么应该已经安装了包含node-static外部模块的node-static npm包。如果没有,你可以执行下面的npm命令进行安装:

第三行代码有一点技巧:

non-static模块想要在Node.js服务器提供多个文件服务对象给客户端使用而不仅仅是被限制在单个文件服务对象上。因此,它没有使用模块本身作为文件服务对象,而是通过调用构造函数来创建一个文件服务对象。构造函数被设计成跟new关键字一起使用。使用new关键字调用构造函数就可以创建一个新的对象。

在这个例子里,模块对象被命名为static。在这个模块对象内部有一个键值对(key-value),key是Server,value是一个构造函数。点操作符(.)指明了这种关系,使得new关键字正确作用在构造函数上。创建一个新的文件服务对象并存在file变量里。

文件服务对象的构造函数接受零个或一个参数。当没有提供参数的时候,文件服务对象会使用当前的目录(文件夹)作为HTTP服务器的顶级目录。例如,如果 httpsvr.njs是在ferris目录下运行的,那么当浏览器如Google Chrome访问http://127.0.0.1:1337/hello.html时,文件服务对象会在ferris目录下寻找hello.html文件。当浏览器访问http://127.0.0.1:1337/exit/goodbye.html时,文件服务对象会在ferris目录下的exit目录里寻找goodbye.html文件。

然而,当使用有参的构造函数时,文件服务对象会到参数所指定的目录内查询文件。例如,当“..”字符串作为构造函数的参数时,被创建出来的文件服务对象会去当前目录的父目录里查询文件。

require()函数只接受一个参数,即要加载的模块的名字。这里并没有提供更便捷的方式在用来在模块加载过程中传递额外参数。虽然它需要指定加载文件的目录作为参数,但是最好还是把加载模块和指定从哪里加载文件的操作完全分开。

在文件服务对象创建好以后,HTTP服务器对象就可以接受HTTP请求并把文件返回给客户端,例如Web浏览器:

上面的代码可以改写成下面三条语句,更容易理解:

第一条语句由三行代码组成。它定义了一个叫handleReq的变量,这个变量的值不是类似字符串或数字这样“一般”的值,而是一个函数。在Node.js里面,可以将函数赋值给一个变量,像字符串和数字一样。当一个函数被赋值给一个变量时,这个函数被叫做回调函数,并为了方便起见,被赋值的变量被叫做回调变量。回调变量的定义和常规函数定义基本相同,不同的是回调函数不需要命名可以通过赋值给变量来使用。

在这个例子里,回调函数需要两个变量。第一个变量req,包含进行HTTP请求的所有数据。第二个变量,res包含HTTP响应的所有数据。在这种实现里,文件服务对象file负责解析HTTP请求,在磁盘上找到对应的文件,然后把数据写入到HTTP响应中,文件就这样被返回到浏览器中。Node-static模块就是依据这种思想来设计的,仅需一行代码文件服务对象就能返回磁盘文件。

第四行代码创建了一个HTTP服务器和一个处理HTTP请求的循环,它会一直接收新的HTTP请求并使用handleReq回调函数来执行这些请求:

在createServer()函数内部,handleReq变量会按如下方式被调用:

回调变量与一般的函数一样(但不同于其他类型的变量)可以调用它所包含的函数。正如你可以看到的,调用handleReq回调函数的参数与调用一般的函数是相同的。事实就是这样的,即使handleReq不是函数的名字仅仅是回调变量或参数的名字。回调变量可以作为参数传递给其他函数,就像其他类型的变量一样。

为什么不直接将file.serve()调用直接硬编码到createServer()函数中呢?难道提供文件不是一个Web服务器该做的事情吗?

这样也是可以的,但是把回调函数传递给createServer函数会更加灵活。记住:http模块是node.js内建的,而node static模块是一个独立安装的npm包。如果将file.serve()调用写到createServer()函数中,当我们使用另一个模块替代node static模块或者添加一些自定义的处理HTTP请求的代码时,就需要复制粘贴整个createServer()函数才能调整其中一行代码。因此,它使用回调函数。所以,你仔细想想,回调函数是一种调用代码的时将自己的代码插入到被调用的函数中的方法。它是在不修改函数本身代码的情况下改变函数的行为的方法。在本例中被调用的函数createServer()必须期望和支持回调函数,但是在编写程序的时候就考虑到回调函数的话,那么调用者可以创建一个匹配它的期望的回调函数,这样函数就可以使用整个回调而不用调用代码的任何详细内容。回调函数使得两段代码能在一起工作,即使它们是不同人在不同时间编写的。

在这个例子中,通过传递作为参数的回调函数,调用者可以以适合它的任何方式来处理HTTP请求。但是,在大多数应用场景中,作为参数传递进去的回调函数会在异步请求完成后被调用。在下一个章节中,将会详细讲解回调函数的这类应用。

第五行代码使用svr对象监听‘127.0.0.1’计算机,又名‘localhost’计算机(即运行Node.js服务器的计算机)上的1337端口:

应该被指出的是,处理HTTP请求的循环更可能在listen()函数里而不是createServer()函数。但是这里的目的是解释回调函数,所以这不是大问题。

因为svr变量和handleReq变量仅被使用了一次因此可以使用更简洁的代码来替换他们,这样三条语句可以合并成一条:

httpsvr.njs的最后一行代码会向控制台中输出一条信息,这样当某人启动HTTP服务器时,他可以知道该如何访问它:

httpsvr.njs文件构建了一个基本的Node.js HTTP服务器。现在我们将组成Web应用程序的所有的文件从PHP服务器中移动到httpsvr.njs所在的文件夹中。当httpsvr.njs启动后,所有这些文件包括HTML、CSS、客户端JavaScript、图片文件(如PNG文件),以及其他相关的文件都可以被传送给客户端了,可能是浏览器,它们就会像之前那样正常工作。客户端仅需要指向正确的端口(例如,1337)就可以从Node.js创建的服务器加载文件了。现在Web应用程序会出问题的唯一原因是它仍然是用PHP写的,但是因为HTML,CSS,客户端JavaScript和图片文件都是完全在客户端处理和执行的,所以除非需要PHP文件的支持,它们都能正常工作。.php文件也可以被移动到Node.js服务器,但是因为Node.js不能解释.php文件,所以它们并不能正常工作。

.php文件和其他文件的最大的不同点是.php文件会被服务器解释,解释结果会作为HTTP响应返回给客户端。而对于其他文件,服务器会读取它们的内容并直接写入到HTTP响应中。如果.php文件没经过解析,那么客户端会接收到PHP源码,而它并不知道如何使用这些源码。客户端需要这些源码的解析结果才能工作,而不是源码本身。PHP到Node.js的转换过程归结来说就是,使用Node.js代码来产生PHP代码完全相同的输出结果。这看起来相当简单,但是工作量很大。

首先,需要为每一个.php文件创建一个本地模块。对于本书来说,一个本地模块就是一个本地的.njs文件,main函数文件可以通过require()函数来加载这个模块。为了给一个.php文件创建一个Node.js模块,我们可以在.php文件相同的文件夹创建一个相同文件名的.njs空文件。例如,对于admin/index.php,创建一个空的admin/index.njs文件。

对于这些转换工作,你需要自己做出判断。在某些情况下,有相当多的.php文件,创建对应的.njs文件会做很多无用功,因此开始的时候只创建一小部分会比较好。但是在其他情况,只有很少的.php文件需要转换,所以一次性创建所有的对应的空文件会比较有效率。

一旦你创建好了对应的.njs文件后,选择一个.php文件并修改它对应的.njs文件:

把这些代码写到.njs文件中并且修改res.end()函数调用的路径(在这个例子中是admin/index.njs)指向正在被修改的这个.njs文件。

通过这段简单的代码,这个.njs文件就是一个实现了exports.serve()这个存根函数的Node.js模块了。存根实现是指那些使用简单代码作为占位符,稍后再用一个更可靠的实现来替换。exports.serve()存根函数有两个参数,它们对应于httpsvr.njs中传递给http.createServer()的回调函数的两个参数。Req参数是HTTP request对象,res参数是HTTP response对象。

当exports.serve()函数被调用的时候,存根实现会返回一个HTTP response,它的一个HTTP 头的Content-Type被设置为“text/plain”,内容则是正在被调用的文件。通过自定义的存根实现使得后期更容易调试,以免一个编码错误导致请求一个文件的HTTP request意外结束在另一个文件中。

一旦你有了一个实现了exports.serve的.njs文件,你应该在适当的时机修改httpsvr.njs文件来调用exports.serve函数。但是首先,必须使httpsvr.njs能访问这个模块。通过使用Node.js中的require()函数可以像加载内建模块一样加载本地模块:

在加载本地模块的时候,参数需要一个以./开头的路径名。./指明了一个路径名是一个本地模块而不是内建模块或npm包。

在这个例子中,该本地模块被赋值给admin_index变量。使用那些包含了实现了.php文件同等功能的Node.js代码的模块文件的路径名的变体作为存储模块的变量的名字会带来很大的便利并且不容易混淆,当然这取决这个Web应用程序中的.php文件的数量(即对应的.njs文件的数量)。

为了将对某个PHP页面的HTTP request传递到正确的Node.js本地模块,我们需要修改传递给http.createServer()的回调函数验证并传递HTTP request到适当的Node.js本地模块。通过在if语句中使用一个简单比较,可以检查该HTTP request是否正在请求一个特定的.php文件,如果是,则把它传递到合适的Node.js本地模块上,否则传递到node static npm包(例如,file变量)。

这段源码使用了Node.js内建的http和url模块实现了一个简单但是功能完整的Node.js服务器。如果需要的话,可以安装使用一些更复杂的包,如express Node.js包。

在这个实现中,需要使用内建的url模块来从HTTP request中解析URL。下面的代码使用require()函数访问内建的url模块:

让我们继续前面的例子,当HTTP request正在请求/admin/index.php URL资源,那么本地模块admin_index会解释这个请求并返回一个HTTP response,而不是使用file变量读取一个文件并以静态文件返回。

你应该已经注意到了HTTP request中URL是与/admin/index.php进行对比的,而不是/admin/index.njs。因为浏览器使用自定义的代码处理对.php文件的请求,它可以以任何方式来处理.php文件的请求,包括忽略磁盘上.php文件并使用Node.js代码来代替运行。一个HTTP request是一个请求,而不是一个需求,Node.js服务器可以不适用完全不适用PHP的情况下处理.php文件的请求。在这段Node.js代码中,.php文件引用变成了进行一个特定操作的唯一标示符,.php扩展符不再代表PHP。回调函数中if-else语句仅仅是将一个唯一的路径匹配到一段Node.js代码上。这个语义表明.php扩展符已经被忽略了。

最有可能的是,客户端正在请求一个.php文件或者是由于用户点击了一个HTML页面中的链接,又或者这是JavaScript AJAX调用的一部分。为了改变这种情况,HTML和客户端JavaScript中对.php文件的引用需要改成.njs。这些改变取决你的实际情况。从有利的一方面来说,移除.php文件扩展符可以消除访问你代码的程序员的困惑,他们可能会奇怪为什么在Node.js代码库里会有PHP引用。从不利的方面来看,移除.php文件引用会在PHP和Node.js代码库之间产生一些不必要的技术上的差异。

在一步一步解释完单个.php文件后,让我们把所有这些融合到一起来处理多个.php文件:

这里有两个重要的修改:(1)添加了一组require()函数调用,每一个require()函数对应于一个.php文件;(2)添加了一组else-if语句与每一个.php文件一一对应。在这个例子中,这个修改过的httpsvr.njs函数会处理四个.php文件:index.njs、login.njs、admin/index.njs和admin/login.njs。这些文件里面的代码都相同,除了对指向那个文件的路径名的修改:

每一个.njs文件中的exports.serve()函数中所包含的代码完整地模拟了它对应的.php文件的操作。我们将会移除存根实现中仅返回.njs文件的名字作为HTTP response的操作,而将对应的.php文件中的PHP代码复制粘贴到exports.serve()函数中,并且通过一系列的变形和转换的技巧将这些PHP代码变成相同功能的Node.js代码。但是我们还没有准备好这样做。

当一个支持PHP的Web服务器执行一个PHP页面时,它并不是仅提供一个未处理的对某个页面的HTTP request,然后执行这个页面。如果它这样做的话,那么每一个PHP页面都需要大量额外的代码来解析原始的HTTP request并且把这些值用更方便的方式存储起来。相反,PHP引擎解码原始的HTTP请求,并将数据填充到一堆众所周知的PHP全局变量中。这些全局变量被正确填充才能保证PHP页面正常工作。

由于我们采用的基本方法是将PHP页面拷贝到本地模块中并将其转换成Node.js代码,那么我们需要自己在Node.js中实现这些全局变量以保证转换过的页面能正常工作。通过分析PHP页面,我们可以决定它依赖于哪些变量。并不是每一个PHP引擎提供的全局变量都需要实现。相反,我们仅实现那些被使用的到的变量。

有五个PHP预定义的全局变量是最常用的:$_GET、$_POST、$_COOKIE、$_REQUEST和$_SESSION。

一个HTTP request被发送时总是有一个HTTP操作,它被叫作方法或者动词。一个HTTP GET操作是非常简单的:客户端向服务器请求获取一个页面。当用户在浏览器的地址栏里键入URL时,他就是在输入一个HTTP GET request。

HTTP GET request可能有一些以名称/值对形式的参数。这些参数通常叫做查询参数或者查询字符串。用户可以手动向浏览器的地址栏中的URL最后添加添加一个问号(?)并将名称/值对以&符号分割添加到后面。键值对本身之间以等号分割。这有一个例子:

在这个例子中的键值对有:theme=green、tab=users和fastload=true。当一个PHP页面获取到一个像这个例子中一样的GET request时,PHP引擎从原始HTTP GET request中提取出这些键值对并把它们放到预定义的PHP$_GET数组。名字作为$_GET数组中的键值或索引,值就是值。对于之前的URL例子,$_GET数组看起来就像这样:

当PHP页面被转换成Node.js代码后,Node.js仍然需要这些预定义的数组存在并被正确填充。接下来的代码展示了一个Node.js函数 initGET(),它可以被用在任何转换过的PHP页面的本地模块中用来填充一个Node.js_GET变量,这个变量就像PHP中的$_GET变量一样工作:

Node.js函数initGET()需要三个参数:req、pre和cb。req参数包含一个原始的HTTP request。pre参数是一个包含了所有预定义的全局变量的Node.js对象。所有预定义的变量都存储在pre变量中,而不是一堆不同的变量,这样可以方便地传递它。而cb包含了一个在initGET()函数结束时会被调用的回调函数。由于initGET()函数仅进行了一些简单的内存操作并且无需进行有回调函数的操作,因此在技术上并不需要回调函数。但是,由于稍后将要实现的initPOST()函数将会需要一个回调函数cb()作为参数,所以最好让initGET()和initPOST()函数保持一致。

initGET()函数的第一行代码在参数pre中创建了一个名为_GET的数组。pre._GET 就是相当于PHP中$_GET数组的Node.js对象。接下来再从main URL中提取出查询参数,而这个main URL则是从req.url属性获取的。通过使用split()函数来将每一个URL查询参数分离开来进而区分它们的名字/值对使得填充pre._GET变量也非常简单。最后,调用cb参数让回调函数知道pre._GET变量已经可用。

为了初始化Node.js pre._GET变量,需要对exports.serve()函数进行一些修改。这里是最初的exports.serve()函数:

这里我们并不是在exports.serve()函数中实现真实的页面,而是使用一个新的函数叫作page(),exports.serve()将被保留作为初始化和完成其他PHP引擎为PHP页面做的工作:

page()函数需要四个参数:req、res、pre和cb。req和res参数代表HTTP request和HTTP response。pre参数是预定义的变量,包含了存储查询参数的_GET属性。cb参数是一个回调函数,它可以让exports.serve()函数知道什么时候页面被完全处理完了。

将pre对象打印出来可以帮助调试。通过使用require()函数加载内建的util模块,并在res.end()函数调用中添加一个util.inspect()函数调用就可以把pre变量中包括_GET属性的所有内容显示在HTTP response中:

现在处理页面的操作已经移动到page()函数中了,exports.serve()函数被修改成进行初始化工作,包括调用initGET()函数:

pre变量是最先被创建的。然后调用initGET()函数,当它完成时,则调用page()函数。在page()函数之后并没有终止化或清除操作,所以它的回调函数是空的。

当_GET属性被实现之后,就可以修改page()函数使用查询参数。修改page()函数,期待接收一个x查询参数并返回对应的值:

如果Node.js服务器正在运行,并且浏览器被指向到http://localhost: 1337/index.php?x=4,浏览器会显示“The value of x is 4.”。

HTTP POST request和HTTP GET request基本一样,除了键值对是通过request正文而不是URL最后的查询字符串进行发送的。一个HTTP request包括HTTP header和一个HTTP body。包含查询字符串的URL便是HTTP header中的一个。HTTP header内容精简并且有长度限制的。尤其是包含查询字符串的URL,需要限制在一定长度,不推荐使用过长的URL。相应地,当需要在HTTP request中包含很多数据时,推荐把数据放到HTTP body中作为HTTP POST的一部分。HTTP body跟被限制长度的HTTP header不一样,它可以处理非常大量的数据。对于HTTP POST,HTTP body通常被称为POST数据。

由于HTTP POST中的正文可能非常巨大,所以POST数据通常不是一次性发送的;它在收到变成可用的事件时会被发送。事件是Node.js用来指示某件事情发生的一种方法。例如,一个data事件表明下一个数据块已经从HTTP body中被读出。假如这里有很多数据,Node.js会在读取每一块数据时触发若干事件。

一个时间可以跟回调函数联系到一起,这也被叫作事件处理程序。事件处理程序会在一个事件发生时被执行。

on()函数可以将一个事件和事件处理函数联系到一起。下面的例子说明了如何使用on函数把data事件跟一个数据处理函数绑定在一起,这个数据处理函数会将数据写到控制台:

对于initPOST()函数,pre的_POST属性会被初始化。就像从initGET()函数中重新调用后一样,pre参数是一个包含了所有预定义的全局变量的Node.js对象。一个body变量会被创建出来保存到被读取的HTTP body。on()函数把一个事件处理程序和data事件联系到一起,这个事件处理程序会在数据变为可用之后将其写入到body变量中:

在data事件处理程序中添加的if语句是用来检测HTTP正文是否太长,如果是则会中断连接。写得不好或恶意的客户端可能会发送无限量的数据,在这时该if语句就需要放弃那个发送的大量数据的HTTP request来保护Node.js服务器。

最后,on()函数为end事件绑定一个事件处理程序,它会在整个HTTP正文被读取之后触发。通过简单的split()函数调用,end事件处理程序提取出数据并把它们放到pre._POST变量中:

cb()在最后被调用时,已经有正确的pre._POST变量值可以使用了。

将所有代码放到一起,initPOST()函数的全文显示如下:

对于那些期待HTTP GET request的页面,必须修改exports.serve()函数。而对于那些期待HTTP POST request的页面,exports.serve()函数的代码则是一样的,除了调用initGET()函数的地方替换为initPOST()函数调用。设计就是这样的。尽管initGET()函数不需要一个回调函数,但是给initGET()函数一个回调函数让它和initPOST()函数有一样的参数并且代码几乎相同。

到目前为止,HTTP GET和HTTP POST是最常用的HTTP操作,并且在大多数的情况下,这也是一个Web应用程序仅需要的HTTP操作。还有一些其他的HTTP操作,如HTTP PUT、HTTP DELETE和HTTP HEAD,但是PHP引擎并没有对这些操作提供支持,所有Node.js移植通常也不需要提供支持。

cookie是一个服务器发送到客户端(通常是浏览器)的键值对,客户端会将cookie存起来并将它作为一个HTTP header添加到每一个后续的HTTP请求中。客户端通常将cookie存储在一个可持久化的地方,如硬盘中,因此能在将来的HTTP请求中使用这个cookie,即使客户端被关闭并重新启动后。cookie是服务器提供给客户端的一小段数据,它可以帮助服务器验证客户端,例如让客户端可以自动登录自己的账户。

cookie的HTTP header的名字就是“Cookie”:

在Node.js的HTTP请求中,cookies是存储在HTTP请求的headers.cookie属性中的,在本书中的例子里就是req。

initCOOKIE()与initGET()函数都用于将cookie从相应的HTTP头中提取出来并放到pre._COOKIE变量中,非常相似。获取cookie会更方便:cookie不是附加 在URL最后作为查询字符串而是有自己的属性值。pre._COOKIE变量将会 用来替代PHP引擎为PHP页面提供的PHP $_COOKIE变量:

就像那些期待HTTP GET和HTTP POST请求的页面,那些需要cookie的页面需要修改它们的exports.serve()函数来调用initCOOKIE()函数。initCOOKIE()函数跟initGET()和initPOST()有同样的参数,因此可以使用同样的代码调用initCOOKIE()函数:

当一个页面同时需要处理HTTP GET和HTTP POST两种请求并且还需要用到cookie时,可以通过将一个函数当作另一个函数的回调函数的方法来增强exports.serve()函数初始化的操作。以下代码用于加载pre._GET、pre._POST和pre._COOKIE属性,它们将用于替代PHP预定义变量$_GET、$_POST和$_COOKIE:

在PHP里,$_REQUEST预定义变量包含了在$_GET、$_POST和$_COOKIE中所有的键值对。我们需要将pre._GET、pre_POST和pre._COOKIE变量复制一份来创建 pre._REQUEST:

Node.js中的for…in语句可以从一个Node.js对象找出所有的属性名。下面的代码显示了一个对象obj并且把它所有的属性名打印出来:

这里是Node.js的for…in循环的输出:

在PHP中,foreach…as与Node.js中的for…in工作行为差不多,除了foreach…as会返回PHP中的属性值而不是像for…in在Node.js中返回属性名。

就像其他预定义变量的初始化函数一样,initREQUEST()函数必须被exports.serve()函数调用。因为_REQUEST值是_GET、_POST和_COOKIE的一个复合体,构造_REQUEST值需要的三个值已经赋值给pre变量,因此 initREQUEST()函数必须要在其他函数初始化之后被调用:

initREQUEST()函数就像期待的那样与其他初始化函数使用完全相同的参数并且表现也相同。

在PHP还有一个常用的预定义变量:$_SESSION变量。$_SESSION变量代表了每一个用户对 PHP页面的调用。

initSESSION()函数使用pre._COOKIE变量维护当前用户在sessions变量中的会话。当会话不存在时,它会被创建出来;否则,将从sessions对象中获取:

同样也需要让页面处理会话。通过将一个函数添加到另一个函数的回调函数中来加强exports.serve()初始化函数的能力。下面的代码会加载pre._GET、pre._POST、pre._COOKIE和pre._REQUEST属性,它们是用来代替PHP中预定义的变量$_GET、$_POST、$_COOKIE和$_REQUEST的。

initSESSION()函数是在exports.serve()函数中调用的最后一个函数。它依赖于PHP变量$_COOKIE来维护这个会话。为了保证cookie处于被激活的状态,需要修改page()函数的回调函数将cookie返回给调用者:

就像所期待的那样,initSESSION()函数跟其他初始化函数一样采用完全相同的参数,并且执行方式也几乎相同。

这里我们创建一个本地模块initreq.njs用来将initGET()、initPOST()、initCOOKIE()、initREQUEST()和initSESSION()函数共享给所有其他模块。并且这些函数作为属性赋值给exports变量,从而使它们可以暴露给加载该模块的调用者:

为了使用这个initreq.njs本地模块,首先需要用require()函数加载它。然后,需要将模块的名字initreq置于initreq.njs文件暴露出来的每一个初始函数的引用之前来进行调用。下面的代码显示了这些改变:

现在你对于从PHP到Node.js的转换应该有更好的想法并且知道整个过程是如何工作的了。

httpsvr.njs文件是一个Node.js HTTP服务器。在一个常见的PHP设置中,httpsvr.njs文件类似于一个安装了PHP模块的Apache Web服务器。如果你想要调整Node.js Web服务器来添加一些页面,将URL指定到特定的页面或者执行其他通用的Web服务器的配置时,那么就需要修改httpsvr.njs文件。我们把之前的httpsvr.njs例子再放到这里以便于引用:

对于每一个PHP页面,都会有一个index.njs文件或其他模块文件被创建出来。exports.serve()是Node.js中对应于PHP引擎用来处理某个特定页面代码的函数。如果需要一些额外的预定义变量,初始化代码或者结束代码(例如,在页面完成之后执行的代码),就需要修改exports.serve()函数。exports.serve()函数并不是页面本身,而是“包裹”这个页面的代码:

page()函数就是页面本身。从广义上讲,这个过程就是将PHP文件中的PHP代码复制到page()函数中。然后将page()函数中的PHP代码转换成Node.js代码。当page()函数中只有Node.js代码并且复制过来的PHP代码一点不剩的时候,page()函数就会和PHP代码的行为完全相同,除了它是Node.js而不是PHP。

下面将会展示一个简单的转换示例。

假设现在你有一个简单的showx5.php页面,它同时包含PHP和HTML:

首先,把PHP代码拷贝并粘贴到page()函数中,这会产生比较奇怪的,没有功能的PHP/Node.js混合代码:

接下来,转换第一个PHP代码模块。这里是转换前的片段:

转换之后,它看起来仍然非常简单,除了现在它已经是Node.js代码了:

然后,剩下的HTML代码也会被转换:

它对应的Node.js代码页非常简单:

下面是showx5.njs中完全转换好的page()函数,它实现了showx5.php一样的功能:

下面是完整的showx5.njs文件:

然后修改httpsvr.njs文件,将showx5.php URL指定到showx5.njs本地模块:

假如你将httpsvr.njs、initreq.njs和showx5.njs文件放到同一个目录下并且运行Node.js服务器,那么PHP和Node.js代码执行效果都一样。使用客户端如浏览器去访问下面的URL会得到一样的结果:

第一个URL会请求PHP服务器。第二个URL会请求与之相同的Node.js Web服务器。

虽然showx5示例是微不足道的,但是它演示了三件事情:

在可以将琐碎的PHP页面转换成Node.js后,本书剩下章节将关注于如何转换那些复杂的,真实世界中的PHP页面。在下一章中,我们将讨论回调函数以及代码线性的概念,将会展示如何在将PHP代码粘贴到page()函数之前进行重构以利于之后更容易地转换成Node.js。


相关图书

Node.js 后端全程实战
Node.js 后端全程实战
Node学习指南 第2版
Node学习指南 第2版
Tomcat内核设计剖析
Tomcat内核设计剖析
Node学习指南
Node学习指南
Node应用程序构建——使用MongoDB和Backbone
Node应用程序构建——使用MongoDB和Backbone
Node.js入门经典
Node.js入门经典

相关文章

相关课程