《写给PHP开发者的Node.js学习指南》一第 2 章 简单的Node.js框架2.1 HTTP服务器

本文涉及的产品
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介:

本节书摘来自异步社区《写给PHP开发者的Node.js学习指南》一书中的第2章,第2.1节,作者【美】Daniel Howard,更多章节内容可以访问云栖社区“异步社区”公众号查看

第 2 章 简单的Node.js框架

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

2.1 HTTP服务器

写给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代码:


b69c841ac474a273be8e925374d8bb46f5ebf623

它是如何工作的呢?

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


323d8b0b6d7919b4db496376572c9b4c40e33001

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


34ebb521cb912a90a13446d3f40d87a1207ecde7

第三行代码有一点技巧:


77f8ca3a99fb2277083507da6b0385aa5b528d77

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浏览器:


d46e7667fd24dac5b959bba5a60e0f7c74c45157

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


d5dec80f6279c907cb441a545eb50375109b1cc8

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

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

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


57d5d2cccd04695918ac2ec339b959f4f4e83b0b

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


f7bf4e8aeee13273b01588cd116409c3a75619b0

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

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


51d6b03f5dc059e655360d3540c8304a37ba0f6d

这样也是可以的,但是把回调函数传递给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端口:


ea188ed1c01fa24eca6bcf67e0e0c024cd269af6

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

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


2cb16526f76f594c2cd676863f2357b7958d001e

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


80403ff240af22eb09e0a036d1ff6900836992a1

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文件:


bacb18196c273450ab427f74f04115c028e3e597

把这些代码写到.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()函数可以像加载内建模块一样加载本地模块:


e4ff8fb2893e6c8a6de3d73f9c769220cbaeecae

在加载本地模块的时候,参数需要一个以./开头的路径名。./指明了一个路径名是一个本地模块而不是内建模块或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变量)。


ac49410fb29dca1bbdf156fa4bfc030dac61dbfe

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

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


1206beba491e62334d2bfd5aaf4047601f1a10bc

让我们继续前面的例子,当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文件:


216a28b61e3a1f94c33d2147dc7cf0161fe85c87

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


6d070474e17ca5e275a12286775e98445d76e9df

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

相关文章
|
25天前
|
SQL 安全 PHP
PHP 自发布以来一直在 Web 开发领域占据重要地位,PHP 8 更是带来了属性、刚性类型等新特性。
【10月更文挑战第1天】PHP 自问世以来,凭借其易用性和灵活性,在 Web 开发领域迅速崛起。从简单的网页脚本语言逐步演进为支持面向对象编程的现代语言,尤其自 PHP 5.3 引入命名空间后,代码组织和维护变得更加高效。PHP 7 的性能优化和 PHP 8 的新特性(如属性和刚性类型)进一步巩固了其地位。框架如 Laravel、Symfony、Yii2 和 CodeIgniter 等简化了开发流程,提高了效率和安全性。
38 2
|
26天前
使用Netty实现文件传输的HTTP服务器和客户端
本文通过详细的代码示例,展示了如何使用Netty框架实现一个文件传输的HTTP服务器和客户端,包括服务端的文件处理和客户端的文件请求与接收。
31 1
使用Netty实现文件传输的HTTP服务器和客户端
|
2月前
|
设计模式 SQL 安全
PHP中的设计模式:单例模式的深入探索与实践在PHP的编程实践中,设计模式是解决常见软件设计问题的最佳实践。单例模式作为设计模式中的一种,确保一个类只有一个实例,并提供全局访问点,广泛应用于配置管理、日志记录和测试框架等场景。本文将深入探讨单例模式的原理、实现方式及其在PHP中的应用,帮助开发者更好地理解和运用这一设计模式。
在PHP开发中,单例模式通过确保类仅有一个实例并提供一个全局访问点,有效管理和访问共享资源。本文详细介绍了单例模式的概念、PHP实现方式及应用场景,并通过具体代码示例展示如何在PHP中实现单例模式以及如何在实际项目中正确使用它来优化代码结构和性能。
38 2
|
2月前
|
开发者
HTTP状态码是由网页服务器返回的三位数字响应代码,用于表示请求的处理结果和状态
HTTP状态码是由网页服务器返回的三位数字响应代码,用于表示请求的处理结果和状态
29 1
|
3月前
|
安全 前端开发 PHP
构建与验证表单:传统PHP与Laravel框架的比较分析——探索Web开发中表单处理的优化策略和最佳实践
【8月更文挑战第31天】在 Web 开发中,表单构建与数据验证至关重要。传统 PHP 方法需手动处理 HTML 表单和数据验证,而 Laravel 框架则提供了一种更现代、高效的解决方案。本文通过对比传统 PHP 和 Laravel 的方法,探讨表单构建与验证的最佳实践。Laravel 通过简洁的语法糖、内置的数据过滤和验证机制,显著提升了代码的安全性和可维护性,适用于大型项目或需要快速开发的场景。然而,在追求灵活性的小型项目中,直接使用 PHP 仍是不错的选择。了解两者的优劣,有助于开发者根据项目需求做出最佳决策。
36 0
|
2月前
|
消息中间件 NoSQL Go
PHP转Go系列 | ThinkPHP与Gin框架之Redis延时消息队列技术实践
【9月更文挑战第7天】在从 PHP 的 ThinkPHP 框架迁移到 Go 的 Gin 框架时,涉及 Redis 延时消息队列的技术实践主要包括:理解延时消息队列概念,其能在特定时间处理消息,适用于定时任务等场景;在 ThinkPHP 中使用 Redis 实现延时队列;在 Gin 中结合 Go 的 Redis 客户端库实现类似功能;Go 具有更高性能和简洁性,适合处理大量消息。迁移过程中需考虑业务需求及系统稳定性。
|
3月前
|
缓存 中间件 PHP
Laravel 框架:优雅 PHP Web 开发的典范
【8月更文挑战第31天】
85 0
|
3月前
|
安全 Java 云计算
JSF 应用究竟何去何从?云端部署能否成为其全新突破点?快来一探究竟!
【8月更文挑战第31天】本文介绍了将JavaServer Faces(JSF)应用部署到云平台的过程。首先,根据成本、功能、可靠性和安全性选择合适的云平台。接着,展示了构建简单JSF应用的示例代码。最后,以AWS Elastic Beanstalk为例,详细说明了部署流程。部署至云端可提升应用的可用性、扩展性和安全性。
43 0
|
3月前
|
JavaScript PHP 开发者
PHP中的异常处理与自定义错误处理器构建高效Web应用:Node.js与Express框架实战指南
【8月更文挑战第27天】在PHP编程世界中,异常处理和错误管理是代码健壮性的关键。本文将深入探讨PHP的异常处理机制,并指导你如何创建自定义错误处理器,以便优雅地管理运行时错误。我们将一起学习如何使用try-catch块捕获异常,以及如何通过set_error_handler函数定制错误响应。准备好让你的代码变得更加可靠,同时提供更友好的错误信息给最终用户。
|
3月前
|
移动开发 网络协议 编译器
实战案例3:C语言实现的HTTP服务器
实战案例3:C语言实现的HTTP服务器
130 0