目录
将请求的uri转换为实际的文件名
下图展示了服务器程序的工作过程,这个过程不仅限于 Web 服务器,对于各种服务器程序都是共通的,收发数据的过程也是大同小异的。 各种服务器程序的不同点在于图中(b)客户端通信部分的第一行调用 read 后面的如下部分。
[ 处理请求消息内容 ];
下图中只写了一行,但实际上这里应该是一组处理各种工作的程序,或者说这里才是服务器程序的核心部分。 Web 服务器中,图中的 read 获取的数据内容就是 HTTP 请求消息。服务器程序会根据收到的请求消息中的内容进行相应的处理,并生成响应消息,再通过 write 返回给客户端。请求消息包括一个称为“方法”的命令,以及表示数据源的 URI(文件路径名),服务器程序会根据这些内容向客户端返回数据,但对于不同的方法和 URI,服务器内部的工作过程会有所不同。
最简单的一种情况如图中的例子所示,请求方法为 GET,URI 为一个 HTML 文件名。这种情况只要从文件中读出 HTML 文档,然后将其作为响应消息返回就可以了。不过,按照 URI 从磁盘上读取文件并没有这么简单。如果完全按照 URI 中的路径和文件名读取 ,那就意味着磁盘上所有的文件都可以访问,Web 服务器的磁盘内容就全部暴露了,这很危险。 因此,这里需要一些特殊的机制。
Web 服务器公开的目录其实并不是磁盘上的实际目录,而是如图这样的虚拟目录,而 URI 中写的就是在这个虚拟目录结构下的路径名。因此,当读取文件时,需要先查询虚拟目录与实际目录的对应关系,并将URI 转换成实际的文件名后,才能读取文件并返回数据。举个例子,假设我们的虚拟目录结构如图所示,如果请求消息中的 URI 如下图所示,那么因为 /~user2/…对应的实际目录为 /home/user2/…,所以将 URI 转换成实际文件名后应该:
/~user2/sub-user2/sample.html (网页路径) ==》 /home/user2/sub-user2/sample.html (磁盘目录)
于是,服务器就会根据上述路径从磁盘中读取相应的文件,然后将数据返回给客户端。文件名转换是有特例的,比如 URI 中的路径省略了文件名的情况, 这时服务器会读取事先设置好的默认文件名。例如在浏览器中输入如下网址。
http://www.glasscom.com/tone/
上面这个网址省略了文件名,服务器会在末尾添加默认文件名,如下。 http://www.glasscom.com/tone/index.html在这个例子中,index.html 这个文件名是在服务器中设置好的,服务器会将它添加在目录名的后面。有些 Web 服务器程序还具有文件名改写功能,只要设置好改写的规则,当 URI 中的路径符合改写规则时,就可以将 URI 中的文件名改写成其他的文件名进行访问。当出于某些原因 Web 服务器的目录和文件名发生变化,但又希望用户通过原来的网址进行访问的时候,这个功能非常有用。
运行CGI程序
如果 URI 指定的文件内容为 HTML 文档或图片,那么只要直接将文件内容作为响应消息返回客户端就可以了。但 URI 指定的文件内容不仅限于 HTML 文档,也有可能是一个程序。在这个情况下,服务器不会直接返回文件内容,而是会运行这个程序,然后将程序输出的数据返回给客户端。Web 服务器可以启动的程序有几种类型,每种类型的具体工作方式有所区别,下面我们来看看 CGI 程序是如何工作的。
当需要 Web 服务器运行程序时,浏览器发送的 HTTP 请求消息内容会和访问 HTML 文档时不太一样,我们先从这里开始讲。Web 服务器运行程序时,一般来说浏览器会将需要程序处理的数据放在 HTTP 请求消息中发送给服务器。这些数据有很多种类,例如购物网站订单表中的品名、数量、发货地址等,搜索引擎中输入的关键字也是一个常见的例子。 总之,浏览器需要在发送给 Web 服务器的请求消息中加入一些数据。有两种加入数据的方法。一种是在 HTML 文档的表单中加上 method="GET",通过 HTTP 的 GET 方法,将输入的数据作为参数添加在 URI 后面发送给服务器。另一种方法是在 HTML 文档的表单中加上 method="POST",将数据放在 HTTP 请求消息的消息体中发送给服务器。
收到请求消息之后,Web 服务器会进行下面的工作。首先,Web 服务器会检查 URI 指定的文件名,看一看这个文件是不是一个程序。这里的判断方法是在 Web 服务器中事先设置好的,一般是通过文件的扩展名来进行判断,例如将 .cgi、.php 等扩展名的文件设置为程序,当遇到这些文件时,Web 服务器就会将它们作为程序来对待。也可以设置一个存放程序的目录,将这个目录下的所有文件都作为程序来对待。此外,还可以根据文件的属性来进行判断。如果判断要访问的文件为程序文件,Web 服务器会委托操作系统运行这个程序,然后从请求消息中取出数据并交给运行的程序。如果方法为GET,则将 URI 后面的参数传递给程序;如果方法为 POST,则将消息体中的数据传递给程序。接下来,运行的程序收到数据后会进行一系列处理,并将输出的数据返回给 Web 服务器。程序可以返回各种内容,如表示订单已接受的说明,
或者按照关键字从数据库中搜索出的结果等。无论如何,为了将数据处理的结果返回给客户端,首先需要将它返回给 Web 服务器。这些输出的数据一般来说会嵌入到 HTML 文档中,因此 Web 服务器可以直接将其作为响应消息返回给客户端。输出数据的内容是由运行的程序生成的,Web 服务器并不过问,也不会去改变程序输出的内容。
web服务器的访问控制
正如我们前面讲的,Web 服务器的基本工作方式就是根据请求消息的内容判断数据源,并从中获取数据返回给客户端,不过在执行这些操作之前,Web 服务器还可以检查事先设置的一些规则,并根据规则允许或禁止访问。这种根据规则判断是否允许访问的功能称为访问控制,一些会员制的信息服务需要限制用户权限的时候会使用这一功能,公司里也可以利用访问控制只允许某些特定部门访问。
当根据客户端域名设置规则时,需要先根据客户端 IP 地址查询客户端域名,这需要使用 DNS 服务器。一般我们使用 DNS 服务器都是根据域名查询 IP 地址,其实根据 IP 地址反查域名也可以使用 DNS 服务器。具体来说, 这个过程是这样的。收到客户端的请求消息后,Web 服务器(①)会委托协议栈告知包的发送方 IP 地址,然后用这个 IP 地址生成查询消息并发送给最近的 DNS 服务器(图 ②)。接下来,DNS 服务器找出负责管辖该 IP 地址的 DNS 服务器,并将查询转发给它(图 ③),查询到相应的域名之后返回结果(图 ④),然后 Web 服务器端的 DNS 服务器再将结果转发给 Web 服务器(图 ⑤)。这样一来,我们就可以根据发送方IP地址查询到域名。接下来,为了保险起见,还需要用这个域名查询一下IP 地址,看看结果与发送方 IP 地址是否一致(图 ⑥)。这是因为有一种在 DNS 服务器上注册假域名的攻击方式,因此我们需要进行双重检查,如果两者一致则检查相应的访问控制规则,判断是否允许访问。从图中可以看出,这种方式需要和 DNS 服务器进行多次查询,整个过程比较耗时,因此 Web 服务器的响应速度也会变慢。
如果用户名和密码已设置好,那么情况如下图。通常的请求消息中不包含用户名和密码,因此无法验证用户名和密码(图 ①)。因此,Web 服务器会向用户发送一条响应消息,告诉用户需要在请求消息中放入用户名和密码(图 ②)。浏览器收到这条响应消息后,会弹出一个输入用户名和密码的窗口,用户输入用户名和密码后(图 ③),浏览器将这些信息放入请求消息中重新发送给服务器(图 ④)。然后,Web 服务器 查看接收到的用户名和密码与事先设置好的用户名和密码是否一致,以此判断是否允许访问,如果允许访问,则返回数据(图 ⑤)。
浏览器接受响应并返回内容
当服务器完成对请求消息的各种处理之后,就可以返回响应消息了。这里的工作过程和客户端向服务器发送请求消息时的过程相同。 首先,Web 服务器调用 Socket 库的 write,将响应消息交给协议栈。 这时,需要告诉协议栈这个响应消息应该发给谁,但我们并不需要直接告知客户端的 IP 地址等信息,而是只需要给出表示通信使用的套接字的描述符就可以了。套接字中保存了所有的通信状态,其中也包括通信对象的信息,因此只要有描述符就万事大吉了。接下来,协议栈会将数据拆分成多个网络包,然后加上头部发送出去。 这些包中包含接收方客户端的地址,它们将经过交换机和路由器的转发,通过互联网最终到达客户端。
浏览器接受响应消息后如何显示内容
Web 服务器发送的响应消息会被分成多个包发送给客户端,然后客户端需要接收数据。首先,网卡将信号还原成数字信息,协议栈将拆分的网络包组装起来并取出响应消息,然后将消息转交给浏览器。这个过程和服务器的接收操作相同。接下来,我们来看一看浏览器是如何显示内容的。要显示内容,首先需要判断响应消息中的数据属于哪种类型。Web 可以处理的数据包括文字、图像、声音、视频等多种类型,每种数据的显示方法都不同,因此必须先要知道返回了什么类型的数据,否则无法正确显示。 这时,我们需要一些信息才能判断数据类型,原则上可以根据响应消息开头的 Content-Type 头部字段的值来进行判断。这个值一般是下面这样的字符串。
其中“/”左边的部分称为“主类型”,表示数据的大分类;右边的“子类型”表示具体的数据类型。在上面的例子中,主类型是 text,子类型是html。主类型和子类型的含义都是事先确定好的 ,下表列出了其中主要的一些类型。上面例子中的数据类型表示遵循 HTML 规格的 HTML 文档此外,当数据类型为文本时,还需要判断编码方式,这时需要用charset 附加表示文本编码方式的信息,内容如下。
主类型 | 含义 | 子类型示例 | |
text
|
表示文本数据
|
text/html
text/plain
|
HTML 文档
纯文本
|
image
|
表示图像数据
|
image/jpeg
image/gif
|
JPEG 格式的图片
GIF 格式的图片
|
audio
|
表示音频数据
|
audio/mpeg | MP2、MP3 格式的音频 |
video
|
表示视频数据
|
video/mpeg video/quicktime |
MPEG 格式的视频
Quicktime 格式的视频
|
model
|
表示对物体等的形状和动作进行建模的数据
|
model/vrml
|
VRML 格式的建模数据
|
application
|
除上述以外的数据,Excel、 Word 等应用程序的数据都
属于这一类型
|
application/pdf
application/msword
|
PDF 格式的文档数据
MS
-
WORD 格式的文档
数据
|
message
|
直接存放邮件等消息时使用的类型,表示直接存放
某种格式的消息
|
message/rfc822
|
一般的邮件数据,包含
From:、Date: 等头部数据
|
multipart
|
消息体中包含多个部分的数据
|
multipart/mixed
|
消息体中包含各种不同
格式的数据,其中每个
部分的数据都有单独定
义的媒体类型
|
这里的 utf-8 表示编码方式为 Unicode,如果是 euc-jp 就表示 EUC 编码,iso-2022-jp 表示 JIS 编码,shift_jis 表示 JIS 编码 除了通过 Content-Type 判断数据类型,还需要检查 Content-Encoding头部字段。如果消息中存放的内容是通过压缩或编码技术对原始数据进行转换得到的,那么 Content-Encoding 的值就表示具体的转换方式,通过这个字段的值,我们可以知道如何将消息中经过转换的数据还原成原始数据。Content-Type 字段使用的表示数据类型的方法是在 MIMEB 规格中定义的,这个规格不仅用于 Web,也是邮件等领域中普遍使用的一种方式。不过这种方式也只不过是一种原则性的规范,要通过 Content-Type 准确判断数据类型,就需要保证 Web 服务器正确设置 Content-Type 的值,但现实中并非总是如此。如果 Web 服务器管理员不当心,就可能会因为设置错误导致 Content-Type 的值不正确。因此,根据原则检查 Content-Type 并不能确保总是能够准确判断数据类型。 因此,有时候我们需要结合其他一些信息来综合判断数据类型,例如请求文件的扩展名、数据内容的格式等。比如,我们可以检查文件的扩展名,如果为 .html 或 .htm 则看作是 HTML 文件,或者也可以检查数据的内容,如果是以 <html> 开头的则看作是 HTML 文档。不仅是 HTML 这样的文本文件,图片也是一样。图片是经过压缩的二进制数据,但其开头也有表示内容格式的信息,我们可以根据这些信息来判断数据的类型。不过,这部分的逻辑并没有一个统一的规格,因此不同的浏览器以及不同的版本都会有所差异。
浏览器显示网页内容:访问完成
判断完数据类型,我们离终点就只有一步之遥了。接下来只要根据数据类型调用用于显示内容的程序,将数据显示出来就可以了。对于 HTML文档、纯文本、图片这些基本数据类型,浏览器自身具有显示这些内容的功能,因此由浏览器自身负责显示。不同类型的数据显示操作的过程也不一样,我们以 HTML 文档为例来介绍。HTML 文档通过标签表示文档的布局和字体等样式信息,浏览器需要解释这些标签的含义,按照指定的样式显示文档的内容。实际的显示操作是由操作系统来完成的,浏览器负责对操作系统发出指令,例如在屏幕上的什么位置显示什么文字、使用什么样的字体等。网页中还可以嵌入图片等数据,HTML 文档和图片等数据是分别存在在不同的文件中的,HTML 文档中只有表示图片引用的标签 。在读取文档数据时,一旦遇到相应的标签,浏览器就会向服务器请求其中的图片文件。
这个请求过程和请求 HTML 文档的过程是一样的,就是在 HTTP 请求消息的 URI 中写上图片文件的文件名即可。将这个请求消息发送给 Web 服务器之后,Web 服务器就会返回图片数据了。接下来,浏览器会将图片嵌入到标签所在的位置。JPEG 和 GIF 格式的图片是经过压缩的,浏览器需要将其解压后委托操作系统进行显示。当然,为了避免图片和文字重叠,在显示文字的时候需要为图片留出相应的位置。像 HTML 文档和图片等浏览器可自行显示的数据,就会按照上述方式委托浏览器在屏幕上显示出来。不过,Web 服务器可能还会返回其他一些类型的数据,如文字处理、幻灯片等应用程序的数据。这些数据无法由浏览器自行显示,这时浏览器会调用相应的程序。这些程序可以是浏览器的插件,也可以是独立的程序,无论如何,不同类型的数据对应不同的程序,这一对应关系是在浏览器中设置好的,只要按照这一对应关系调用相应的程序,并将数据传递给它就可以了。然后,被调用的程序会负责显示相应的内容。到这里,浏览器的显示操作就完成了,可以等待用户的下一个动作了。 当用户点击网页中的链接,或者在网址栏中输入新的网址时,访问 Web 服务器的操作就又开始了。