Return to Source (1)

我一直觉得很有必要谈一点很基本的东西,这就是我们的 Web 是如何运作的,或者说,Web 浏览器和 Web 服务器是如何运作的,其间又是如何交互的。因为了解原理之后,许多设计师,或者开发者们的困惑就迎刃而解了。这些困惑包括:我能不能用 ASP(.net) 开发符合标准的网站?网站是不是只有静态的才能符合标准?等等,事实上,在你了解原理之后,就会明白它们都不会成为问题。

首先,Web 服务器,也就是 HTTP 服务器是一个随着操作系统启动而启动的,常住在内存中的程序,它监听着运行着它的这台机器——我们假定它的域名是 www.domain.com——的 (默认是) 80 端口,等待有客户端来进行连接。

下面我们在客户端的地址栏输入网址, 试图连接这台服务器,你输入的 URL——让我们从最简单的开始——http://www.domain.com/foo/bar.html,其中 www.domain.com 指定了要连接到的服务器的域名,下面你的 Web 浏览器就通过你的 DNS 服务器来寻找这个域名对应的 IP 地址。

OK,我们找到了,假定是 192.168.0.1,那么,因为你的 URL 书写中忽略了端口 (本该写成 www.domain.com:80 的形式),默认就采用了 80 端口,于是 Web 浏览器试图连接 192.168.0.1 这台机器的 80 端口,连上以后,通过 HTTP 协议与 Web 服务器交互。

我们不必在这里讨论 HTTP 报文的细节,需要了解的只是在交互中,客户端,也就是你的浏览器需要提供那些信息呢?通常包括:

  1. 要获取的页面地址,这里是 foo/bar.html
  2. 本身的用户代理名称和版本,比如 Mozilla..;
  3. 用户代理希望收到的内容采用的编码和语言,比如 gb2312 zh-CN

下面 Web 服务器收到了这些请求,就要开始研究如何回应了。首先,得找到要返回的 HTML 文档的位置,这个 foo 比较复杂,有可能是 Web 服务器文档的主目录 (在 Apache 服务器下是 httpd.conf 配置文件中的 DocumentRoot 变量定义的) 下的一个真实的目录,也有可能是在 Web 服务器下设置的一个虚拟目录 (Apache 下通过 Alias foo/ 绝对路径 映射到硬盘上的某个目录),总之,它终归是找到了 foo/ 所指的某个硬盘上的目录,否则就会返回 404 错误了。

找到这个目录后,当然是试图读出这个 bar.html 文件,然后把它的内容传输给浏览器。同样,返回的内容也必须包含一系列用 HTTP 协议标记的信息,其中最重要的是 HTTP 头 (header),包括:

  1. 内容的 MIME 类型和编码,类似 Content-Type: text/html; encoding: gb2312。这里指定的编码的优先级是最高的,除非它指定的是 none,否则 HTML 中的 <meta> 标记指定的不起作用。
  2. 内容的请求时间和过期时间。

在 HTTP 头之后,相隔两个 \r\n (回车符和换行符),就是 bar.html 的内容了。

OK,下面用户的浏览器获得了这些信息,对之进行解析和渲染,形成你看到的 Web 页面 (这个渲染过程,会在 Return to Source 的第二部分详细的解释,在第一部分,我们只关注 Server Side)。从上面的叙述,你应该了解了一个对静态页面的请求从客户端到服务器端,再从服务器端回到客户端的全过程。

有了上面的基础,我们可以谈谈动态页面的生成了,现在各式各样的技术层出不穷,颇令人眼花缭乱,CGI、PHP、ASP、ISAPI、modperl、ASP.net 等等等等,但还是让我们回归本原,讨论 CGI 这种最基本和最流行的动态页面处理方式,尔后你才容易明白那些花里胡哨的名词背后其实没有什么,而且很多叫法——比如说把以 .cgi 为后缀的 Perl 脚本成为 CGI 脚本来与 PHP 脚本并称——是错误的。

CGI 的运行流程是相当简单的,这也正是它如此流行的原因之一。首先,客户端发出一个请求,比如 http://www.domain.com/foo/bar.php。服务器端收到这个请求之后,判断 foo 这个目录是否有权限运行 CGI 脚本——这是在服务器的配置文件中限定的。

如果有权限的话,检查 bar.php 的后缀名,发现是 .php,这在我们的 Web 服务器中,已经配置了要用 C:\php\php.exe 程序来处理的。好的,这是一个 CGI 脚本,服务器于是把 foo/bar.php 这个 PHP 程序交给 C:\php\php.exe 去处理,而 php.exe 解释这个 PHP 程序,将得到的内容输出到标准输出端 (stdout, 如果你有一定的编程基础,会比较好理解这个东西,在此你可以简单认为它就是服务器的屏幕),Web 服务器于是截获标准输出端得到的内容,把它返回给用户的浏览器。

或许我们应该更具体地解释这个解释的过程,假设我们有这么一个 bar.php

<html><body><?php print "Hello, World"; ?></body></html>

解析得到的内容就是:

<html><body>Hello, World</body></html>

浏览器得到的正是这个内容。

所以你可以认为 Perl 程序,PHP 程序乃至 ASP、JSP、ASP.net 都是某种形式的 CGI 脚本——对应到不同的解释器,尽管运行机理各异,但总之它们必须把客户端请求的页面进行解释,把解释后得到的内容返回给服务器,这个结构可以说是固定的。所以我们有理由说把“CGI”和“PHP”并称的方式是不恰当的,它们应该是树干和树枝的关系,而不是树枝上的苹果甲和苹果乙的关系。

下面我们有必要谈谈稍微复杂一点的情况,比如说用户浏览器请求的地址是 http://www.domain.com/foo/bar.php?id=1,此时,Web 服务器会把 ? 后的内容提取出来,放到环境变量 (你可能不大了解环境变量是什么东西,但可以简单地理解为在 CGI 脚本中可以访问的内容) ——比如说——HTTP_GET_VARS 中。

本来这个环境变量的内容就是 "id=1" 这样的一个字符串的,但 PHP 显然帮你做了一些额外的工作,把这个变量转换成了一个复杂数组类型的输出,形成了 "id" -> 1 的一一对应关系,于是你可以简单地用 _HTTP_GET_VARS["id"]来得到 1

这个功能 (称为 GET 方式) 可以让用户发送一些额外的数据给服务器端,比如请求的页面的序号、登录的用户 id 等等。而我们在表单 (form) 中提交的内容通常是通过 POST 方式隐式地传给服务器端的,服务器端则通常会把这段内容通过标准输入 (stdin) 发送给 CGI 脚本。

最后让我们回到一开始提出的问题:动态页面是否会影响 Web 标准的实施?我的回答是,一般不存在此类问题,因为所谓 Web 标准,无论是 XHTML、CSS 还是 ECMAScript,都只和客户端,也就是你的浏览器的工作有关,而动态页面负责的只是提供这些内容的文本数据,却不负责渲染这些数据。用户的页面中所有的标记,无论在静态页面中还是动态页面中,都会允许你自由的去写,比如说我当然可以在 PHP 代码中只写一个 <html>,不写相应的关闭符,PHP 解释器才不会检查这些内容,只把它当作一般的字符串处理。但我也可以合法地关闭每个打开的标记啊。

所以说选用某种脚本语言不是问题,服不符合标准,全看你在脚本代码中提供了什么内容,所有这些脚本语言本身提供的功能都不会限制你遵循标准,因为你要生成什么样的 HTML 代码都是允许的:它们只不过是一段字符串啊。

之所以在回答中冠以“一般”,是因为如果你使用了额外的一些功能,可能会引发问题,比如说 PHP 的 PEAR 库提供了 HTMLForm 库帮助你生成表单的 HTML 代码,如果你使用这个 HTMLForm,就必须小心它是否生成了合法的代码喽。ASP(.net) 也同样如此,比如说你在 VS.Net 中开发,通过某些图形化的方式——比如说拖拽控件——构建你的 ASP.net 页面,就得小心它背后隐藏的代码是否符合标准的了。

现在你了解了其间的机理,应该可以明白下面的总结:所谓静态动态,只不过是在服务器端的区别,到了浏览器上,大家都是静态的

Author: jjgod

A software engineer from China, working on text rendering for a fruit company. Interested in typography and science fiction.

6 thoughts on “Return to Source (1)”

  1. ASP和ASP.NET是完全不同的两种脚本呢?还是他们只有运行的环境不同?学ASP.NET需要ASP的基础吗?

Leave a Reply

Your email address will not be published. Required fields are marked *