2. 基础知识
在本章中,我们将介绍 Web 编程的基础知识。其主要目标是阐述 Web 编程的核心原则,这些原则与具体实现技术无关。书中包含大量示例,建议您亲自测试,以便逐步“领悟” Web 开发的理念。测试所需的免费工具列表见文档末尾名为“Web 工具”的附录。
2.1. Web 应用程序的组成部分
服务器

客户端15
编号 | 角色 | 常见示例 |
服务器操作系统 | Linux、Windows | |
Web 服务器 | Apache(Linux、Windows) IIS (NT)、PWS (Win9x)、Cassini (Windows + .NET 平台) | |
服务器端脚本。它们可由服务器模块或服务器外部的程序(CGI)执行。 | PERL (Apache, IIS, PWS) VBSCRIPT(IIS、PWS) JAVASCRIPT(IIS、PWS) PHP(Apache、IIS、PWS) Java (Apache, IIS, PWS) C#、VB.NET (IIS) | |
数据库——可以与使用它的程序位于同一台机器上,也可以通过互联网位于另一台机器上。 | Oracle (Linux, Windows) MySQL (Linux, Windows) Postgres (Linux, Windows) Access(Windows) SQL Server (Windows) | |
客户端操作系统 | Linux、Windows | |
Web 浏览器 | 网景、Internet Explorer、Mozilla、Opera | |
在浏览器中执行的客户端脚本。这些脚本无法访问客户端计算机的磁盘。 | VBScript (IE) JavaScript(IE、Netscape) PerlScript(IE) Java小程序 |
2.2. Web 应用程序中的数据交换 与表单

服务器客户端
编号 | 角色 |
浏览器首次请求一个 URL(http://machine/url)。未传递任何参数。 | |
Web 服务器发送该 URL 对应的网页。该网页可能是静态的,也可能是由服务器端脚本 (SA) 动态生成的,该脚本可能使用了来自数据库 (SB, SC) 的内容。在此,脚本将检测到该 URL 是被无参数请求的,并生成初始网页。 浏览器接收该页面并将其显示出来(CA)。浏览器端脚本(CB)可能会修改服务器发送的初始页面。随后,通过用户(CD)与脚本(CB)之间的交互,网页将发生变化。特别是,表单将被填写。 | |
用户提交表单数据,数据随后被发送至Web服务器。浏览器会根据情况重新加载原始URL或另一个URL,并同时将表单值发送至服务器。此过程可采用两种方法:GET和POST。收到客户端请求后,服务器将执行与该URL关联的脚本(SA),该脚本会检测参数并进行处理。 | |
服务器返回由程序(SA、SB、SC)生成的网页。此步骤与前面的步骤2完全相同。通信现在按照步骤2和3进行。 |
2.3. 符号
下文将假设已安装若干工具,并采用以下符号:
符号 | 含义 |
Apache 服务器目录树的根目录 | |
Apache 提供的网页的根目录。网页必须位于此根目录下。因此,URL http://localhost/page1.htm 对应于文件 <apache-DocumentRoot>\page1.htm。 | |
与 cgi-bin 别名关联的目录树根目录,Apache 的 CGI 脚本可放置于此。因此,URL http://localhost/cgi-bin/test1.pl 对应于文件 <apache-cgi-bin>\test1.pl。 | |
由 IIS、PWS 或 Cassini 提供的网页的根目录。网页必须位于此根目录下。因此,URL http://localhost/page1.htm 对应于文件 <IIS-DocumentRoot>\page1.htm。 | |
Perl 语言目录树的根目录。perl.exe 可执行文件通常位于 <perl>\bin 中。 | |
PHP 目录树的根目录。可执行文件 php.exe 通常位于 <php> 目录下。 | |
Java 目录树的根目录。与 Java 相关的可执行文件位于 <java>\bin 中。 | |
Tomcat 服务器的根目录。Servlet 示例位于 <tomcat>\webapps\examples\servlets,JSP 页面示例位于 <tomcat>\webapps\examples\jsp |
关于这些工具,请参阅附录,其中提供了安装指南。
2.4. 静态网页、动态网页
静态页面由 HTML 文件表示。而动态页面则由 Web 服务器“即时”生成。在本节中,我们将通过使用不同的 Web 服务器和编程语言进行各种测试,以展示 Web 概念的普适性。我们将使用两款 Web 服务器:Apache 和 IIS。虽然 IIS 是一款商业产品,但它还提供两个功能较为有限但免费的版本:
- 适用于 Win9x 系统的 PWS
- 适用于 Windows 2000 和 XP 系统的 Cassini
<IIS-DocumentRoot> 文件夹通常位于 [驱动器:\inetpub\wwwroot],其中 [驱动器] 指安装了 IIS 的磁盘(C、D 等)。PWS 也是如此。对于 Cassini,<IIS-DocumentRoot> 文件夹取决于服务器的启动方式。 附录显示,Cassini 服务器可通过 DOS 窗口(或通过快捷方式)按以下方式启动:
[WebServer] 应用程序(也称为 Cassini Web 服务器)接受三个参数:
- /port: Web 服务的端口号。可以是任意数字。默认值为 80
- /path:磁盘上文件夹的物理路径
- /vpath:与前述物理文件夹关联的虚拟文件夹。请注意,语法应为 /vpath:path,而非 /path=path,这与上文帮助面板中的说明相反。
如果按以下方式启动 Cassini:
那么 P 文件夹即为 Cassini 服务器 Web 目录树的根目录。因此,该文件夹即由 <IIS-DocumentRoot> 指定的目录。因此,在以下示例中:
Cassini 服务器将运行在 80 端口,其 <IIS-DocumentRoot> 目录的根目录即为 [d:\data\devel\webmatrix] 文件夹。待测试的网页必须位于该根目录之下。
接下来,每个 Web 应用程序将由一个文件表示,该文件可使用任何文本编辑器创建。无需使用 IDE。
2.4.1. 静态 HTML 页面 (超文本标记语言)
请看以下 HTML 代码:
<html>
<head>
<title>essai 1 : une page statique</title>
</head>
<body>
<center>
<h1>Une page statique...</h1>
</body>
</html>
生成的网页如下:
测试

测试1
- 启动 Apache 服务器
- 将 test1.html 脚本放置在 <apache-DocumentRoot> 目录下
- 在浏览器中访问 URL http://localhost/essai1.html
- 停止 Apache 服务器
测试2
- 启动 IIS/PWS/Cassini 服务器
- 将脚本 test1.html 放置在 <IIS-DocumentRoot> 中
- 在浏览器中访问 URL http://localhost/essai1.html
2.4.2. 一个 ASP(Active Server Pages)页面
test2.asp 脚本:
<html>
<head>
<title>essai 1 : une page asp</title>
</head>
<body>
<center>
<h1>Une page asp générée dynamiquement par le serveur PWS</h1>
<h2>Il est <% =time %></h2>
<br>
A chaque fois que vous rafraîchissez la page, l'heure change.
</body>
</html>
将生成以下网页:

测试
- 启动 IIS/PWS 服务器
- 将 essai2.asp 脚本放置在 <IIS-DocumentRoot> 目录下
- 使用浏览器访问 URL http://localhost/essai2.asp
2.4.3. 一个 P ERL(实用提取和报告语言)脚本
essai3.pl脚本:
#!d:\perl\bin\perl.exe
($secondes,$minutes,$heure)=localtime(time);
print <<HTML
Content-type: text/html
<html>
<head>
<title>essai 1 : un script Perl</title>
</head>
<body>
<center>
<h1>Une page générée dynamiquement par un script Perl</h1>
<h2>Il est $heure:$minutes:$secondes</h2>
<br>
A chaque fois que vous rafraîchissez la page, l'heure change.
</body>
</html>
HTML
;
第一行是 perl.exe 可执行文件的路径。如有必要,您可能需要调整它。一旦由 Web 服务器执行,该脚本将生成以下页面:

该
- Web 服务器:Apache
- 作为参考,请查看 <apache>\confs 目录下的 srm.conf 或 httpd.conf 配置文件(具体取决于您的 Apache 版本),查找提及 cgi-bin 的行,以确定应放置 essai3.pl 的 <apache-cgi-bin> 目录。
- 将 essai3.pl 脚本放置在 <apache-cgi-bin> 目录中
- 访问 URL http://localhost/cgi-bin/essai3.pl
请注意,Perl 页面的加载时间比 ASP 页面更长。这是因为 Perl 脚本由 Perl 解释器执行,该解释器必须在运行脚本前加载。它不会永久驻留内存中。
2.4.4. PHP 脚本
essai4.php 脚本
<html>
<head>
<title>essai 4 : une page php</title>
</head>
<body>
<center>
<h1>Une page PHP générée dynamiquement</h1>
<h2>
<?
$maintenant=time();
echo date("j/m/y, h:i:s",$maintenant);
?>
</h2>
<br>
A chaque fois que vous rafraîchissez la page, l'heure change.
</body>
</html>
上述脚本生成的网页如下:

测试
测试1
- 请检查位于 <Apache>\confs 目录下的 srm.conf 配置文件或 Apache 的 httpd.conf 文件
- 作为参考,请检查 PHP 配置行
- 启动 Apache 服务器
- 将 essai4.php 放置在 <apache-DocumentRoot> 目录下
- 访问 URL http://localhost/essai4.php
Test2
- 启动 IIS/PWS 服务器
- 请参考 PWS 中关于 PHP 的配置
- 将 essai4.php 放置在 <IIS-DocumentRoot>\php 中
- 请求 URL http://localhost/essai4.php
2.4.5. 一个 JSP 脚本
heure.jsp 脚本
<% //programme Java affichant l'heure %>
<%@ page import="java.util.*" %>
<%
// code JAVA pour calculer l'heure
Calendar calendrier=Calendar.getInstance();
int heures=calendrier.get(Calendar.HOUR_OF_DAY);
int minutes=calendrier.get(Calendar.MINUTE);
int secondes=calendrier.get(Calendar.SECOND);
// heures, minutes, secondes sont des variables globales
// qui pourront être utilisées dans le code HTML
%>
<% // code HTML %>
<html>
<head>
<title>Page JSP affichant l'heure</title>
</head>
<body>
<center>
<h1>Une page JSP générée dynamiquement</h1>
<h2>Il est <%=heures%>:<%=minutes%>:<%=secondes%></h2>
<br>
<h3>A chaque fois que vous rechargez la page, l'heure change</h3>
</body>
</html>
该脚本经 Web 服务器执行后,将生成以下页面:

测试
- 将 heure.jsp 脚本放置在 <tomcat>\jakarta-tomcat\webapps\examples\jsp(Tomcat 3.x)或 <tomcat>\webapps\examples\jsp(Tomcat 4.x)中
- 启动 Tomcat 服务器
- 访问 URL http://localhost:8080/examples/jsp/heure.jsp
2.4.6. 一个 ASP.NET 页面
heure1.aspx 脚本:
<html>
<head>
<title>Démo asp.net </title>
</head>
<body>
Il est <% =Date.Now.ToString("hh:mm:ss") %>
</body>
</html>
该脚本由 Web 服务器执行后,将生成以下页面:

此测试需要一台已安装 .NET 平台的 Windows 计算机(参见附录)。
- 将 heure1.aspx 脚本放置在 <IIS-DocumentRoot> 中
- 启动 IIS/CASSINI 服务器
- 访问 URL http://localhost/heure1.aspx
2.4.7. 结论
前面的示例表明:
- HTML 页面可以由程序动态生成。这正是 Web 编程的全部意义所在。
- 所使用的语言和 Web 服务器可以各不相同。目前,观察到以下主要趋势:
- Apache/PHP(Windows、Linux)和 IIS/PHP(Windows)的组合
- Windows 平台上的 ASP.NET 技术,该技术将 IIS 服务器与 .NET 语言(如 C#、VB.NET 等)相结合
- 在各种服务器(Tomcat、Apache、IIS)和各种平台(Windows、Linux)上运行的 Java Servlet 技术和 JSP 页面。
2.5. 浏览器端脚本
HTML 页面可以包含由浏览器执行的脚本。存在多种浏览器端脚本语言,以下列举几种:
语言 | 支持的浏览器 |
VBScript | IE |
JavaScript | IE、Netscape |
PerlScript | IE |
Java | IE、Netscape |
让我们来看几个例子。
2.5.1. 一个包含 VBScript 脚本的网页,在浏览器端
vbs1.html 页面
<html>
<head>
<title>essai : une page web avec un script vb</title>
<script language="vbscript">
function reagir
alert "Vous avez cliqué sur le bouton OK"
end function
</script>
</head>
<body>
<center>
<h1>Une page Web avec un script VB</h1>
<table>
<tr>
<td>Cliquez sur le bouton</td>
<td><input type="button" value="OK" name="cmdOK" onclick="reagir"></td>
</tr>
</table>
</body>
</html>
上面的 HTML 页面不仅包含 HTML 代码,还包含一个旨在由加载此页面的浏览器执行的程序。代码如下:
<script language="vbscript">
function reagir
alert "Vous avez cliqué sur le bouton OK"
end function
</script>
<script></script> 标签用于在 HTML 页面中界定脚本。这些脚本可以使用多种语言编写,而 <script> 标签的 language 属性则指定了所使用的语言。在此示例中,使用的是 VBScript。我们在此不详细讨论该语言。上面的脚本定义了一个名为 react 的函数,用于显示一条消息。该函数何时被调用?以下一行 HTML 代码告诉了我们:
onclick 属性指定了用户点击“OK”按钮时要调用的函数名称。一旦浏览器加载了此页面且用户点击了“OK”按钮,将显示以下页面:

测试
只有 Internet Explorer 能够执行 VBScript 脚本。Netscape 需要安装插件才能执行。我们可以进行以下测试:
- Apache 服务器
- 位于 <apache-DocumentRoot> 目录下的 vbs1.html 脚本
- 使用 Internet Explorer 访问 URL http://localhost/vbs1.html
- IIS/PWS 服务器
- 将 vbs1.html 脚本放置在 <pws-DocumentRoot> 目录下
- 使用 Internet Explorer 请求 URL http://localhost/vbs1.html
La page : js1.html
<html>
<head>
<title>essai 4 : une page web avec un script Javascript</title>
<script language="javascript">
function reagir(){
alert ("Vous avez cliqué sur le bouton OK");
}
</script>
</head>
<body>
<center>
<h1>Une page Web avec un script Javascript</h1>
<table>
<tr>
<td>Cliquez sur le bouton</td>
<td><input type="button" value="OK" name="cmdOK" onclick="reagir()"></td>
</tr>
</table>
</body>
</html>
这与上一页完全相同,只是我们将 VBScript 替换为 JavaScript。JavaScript 的优势在于同时受到 Internet Explorer 和 Netscape 的支持。运行此代码会产生相同的结果:

测试
- Apache 服务器
- 位于 <apache-DocumentRoot> 目录下的 js1.html 脚本
- 使用 Internet Explorer 或 Netscape 访问 URL http://localhost/js1.html
- IIS/PWS 服务器
- 位于 <pws-DocumentRoot> 目录下的 js1.html 脚本
- 使用 Internet Explorer 或 Netscape 请求 URL http://localhost/js1.html
2.6. 客户端-服务器通信
让我们回到最初展示 Web 应用程序组件的图表:

服务器
在此,我们将重点关注客户端与服务器端之间的交互。这些交互通过网络进行,因此有必要回顾两台远程机器之间交互的一般结构。
2.6.1. OSI模型
由国际标准化组织(ISO)定义的开放网络模型——即 OSI(开放系统互连参考模型)——描述了一个理想的网络环境,其中机器间的通信可通过七层模型来表示:

每一层从下层接收服务,并向上层提供自身的服务。假设位于不同机器 A 和 B 上的两个应用程序想要通信:它们在应用层进行通信。它们无需了解网络运作的所有细节:每个应用程序将希望传输的信息传递给下层——即表示层。 因此,应用程序只需了解与表示层交互的规则。一旦信息进入表示层,它就会根据其他规则传递给会话层,依此类推,直到信息到达物理介质并被物理传输到目标机器。在那里,它将经历与发送机器上相反的过程。
在每一层,负责发送信息的发送方进程都会将其发送给另一台机器上与其处于同一层的接收方进程。这一过程遵循被称为“层协议”的特定规则。因此,我们得到如下最终通信图:

各层的作用如下:
确保通过物理介质传输比特。该层包括数据处理终端设备(DPTE),如终端或计算机,以及数据电路终端设备(DCTE),如调制解调器、复用器和集中器。该层的关键点包括: . 信息编码方式的选择(模拟或数字) . 传输模式的选择(同步或异步)。 | |
隐藏物理层的物理特性。检测并纠正传输错误。 | |
管理信息在网络上必须遵循的路径。这被称为路由:确定信息到达目的地必须经过的路线。 | |
实现两个应用程序之间的通信,而前几层仅允许机器之间的通信。该层提供的一项服务是多路复用:传输层可利用单一网络连接(机器间连接)来传输属于多个应用程序的数据。 | |
该层提供服务,允许应用程序在远程机器上建立并维持工作会话。 | |
其目的是规范不同机器间数据的表示形式。因此,来自机器A的数据在通过网络发送之前,会由机器A的表示层按照标准格式进行“格式化”。当数据到达目标机器B的表示层时,该层会根据标准格式识别这些数据,并对其进行重新格式化,以便机器B上的应用程序能够识别它们。 | |
在此层,我们看到通常与用户密切相关的应用程序,例如电子邮件或文件传输。 |
2.6.2. TCP/IP模型
OSI模型是一个理想模型。TCP/IP协议套件对其近似实现如下:

- 网络接口(计算机的网卡)执行 OSI 模型第 1 层和第 2 层的功能
- IP(互联网协议)层执行 OSI 模型第 3 层(网络层)的功能
- TCP(传输控制协议)或 UDP(用户数据报协议)层执行第 4 层(传输层)的功能 TCP 协议确保机器之间交换的数据包能够到达目的地。如果未能到达,它会重发丢失的数据包。UDP 协议不执行此任务,因此需要由应用程序开发人员来处理。这就是为什么在互联网上——一个并非 100% 可靠的网络——TCP 协议被广泛使用。这被称为 TCP/IP 网络。
- 应用层涵盖了OSI模型中第5层至第7层的功能。
Web 应用程序位于应用层,因此依赖于 TCP/IP 协议。客户端和服务器机器的应用层之间交换消息,这些消息随后被传递给模型的第 1 层至第 4 层,以便路由至目的地。为了相互通信,两台机器的应用层必须“使用”相同的语言或协议。 Web应用程序使用的协议称为HTTP(超文本传输协议)。这是一种基于文本的协议,意味着机器通过网络交换文本行来通信。这些交换是标准化的,即客户端有一组消息来精确告知服务器其需求,而服务器也有一组消息向客户端提供响应。这种消息交换采用以下形式:

客户端 --> 服务器
当客户端向 Web 服务器发出请求时,它会发送
- 以 HTTP 格式编写的文本行来表明其需求
- 空行
- 可选的文档
服务器 --> 客户端
当服务器向客户端响应时,它会发送
- 以 HTTP 格式发送文本行,以表明其正在发送的内容
- 空行
- 可选地发送一个文档
因此,双向通信均遵循相同的格式。在两种情况下都可能发送文档,尽管客户端向服务器发送文档的情况较为罕见。但HTTP协议允许这种操作。正是这一特性使得ISP的用户能够将各种文档上传至由该ISP托管的个人网站。交换的文档可以是任何类型。以浏览器请求包含图片的网页为例:
- 浏览器连接到 Web 服务器并请求所需的页面。被请求的资源通过 URL(统一资源定位符)进行唯一标识。浏览器仅发送 HTTP 头,而不发送文档。
- 服务器作出响应。它首先发送 HTTP 头部,表明将发送何种类型的响应。如果请求的页面不存在,这可能是错误响应。如果页面存在,服务器将在响应的 HTTP 头部中表明,随后将发送一个 HTML(超文本标记语言)文档。该文档是一系列 HTML 格式的文本行。 HTML文本包含标签(标记),这些标签为浏览器提供了如何显示文本的指令。
- 客户端通过服务器的 HTTP 头得知将收到一个 HTML 文档。它会解析该文档,并可能发现其中包含图像引用。这些图像并未包含在 HTML 文档中。因此,它会向同一台 Web 服务器发出新请求,以获取所需的第一个图像。此请求与步骤 1 中的请求完全相同,只是请求的资源不同。 服务器将处理此请求,向客户端发送所请求的图像。此次,在响应中,HTTP 头部将明确指出所发送的文档是图像而非 HTML 文档。
- 客户端接收所发送的图像。步骤3和4将不断重复,直到客户端(通常是浏览器)获取到显示整页所需的所有文档为止。
2.6.3. HTTP 协议
让我们通过实例来探索 HTTP 协议。浏览器和 Web 服务器之间会交换什么?
2.6.3.1. 来自 HTTP 服务器的响应
在此,我们将探讨 Web 服务器如何响应来自客户端的请求。Web 服务或 HTTP 服务是一种通常运行在 80 端口的 TCP/IP 服务。它也可以运行在其他端口上。在这种情况下,客户端浏览器需要在请求的 URL 中指定该端口。URL 通常遵循以下格式:
其中
协议 | 对于 Web 服务,协议为 http。浏览器还可以作为 FTP、新闻、Telnet 及其他服务的客户端。 |
主机 | 托管 Web 服务的机器名称 |
端口 | Web 服务端口。如果端口号为 80,则可以省略。这是最常见的情况 |
路径 | 请求资源的路径 |
info | 提供给服务器的附加信息,用于说明客户端的请求 |
当用户请求加载一个 URL 时,浏览器会做什么?
- 它会与URL中machine[:port]部分指定的主机和端口建立TCP/IP连接。建立TCP/IP连接意味着在两台机器之间创建一条通信“通道”。一旦该通道建立,两台机器之间交换的所有信息都将通过它传输。建立这条TCP/IP通道时,尚未涉及Web的HTTP协议。
- TCP-IP连接建立后,客户端通过发送符合HTTP格式的文本行(命令)向Web服务器发送请求。它将URL中的路径/信息部分发送给服务器
- 服务器将通过同一连接以相同方式进行响应
- 双方中的一方将决定关闭连接。这取决于所使用的HTTP协议。在HTTP 1.0中,服务器会在每次响应后关闭连接。这迫使需要多次请求以获取构成网页的各种文档的客户端,为每次请求都建立新的连接,从而产生额外开销。 在 HTTP/1.1 协议中,客户端可以指示服务器保持连接打开,直到客户端要求关闭为止。因此,客户端可以使用单一连接获取网页的所有文档,并在获取最后一个文档后自行关闭连接。服务器将检测到此关闭操作并随之关闭连接。
为了分析客户端与 Web 服务器之间的交互,我们将使用一个名为 curl 的工具。Curl 是一款 DOS 应用程序,允许您作为客户端访问支持多种协议(HTTP、FTP、TELNET、GOPHER 等)的互联网服务。 Curl 可通过 URL http://curl.haxx.se/ 获取。在此,我们建议下载 Windows 版本 win32-nossl,因为 win32-ssl 版本需要额外的 DLL 文件,而这些文件未包含在 curl 软件包中。 该软件包包含一组文件,只需将其解压到一个文件夹中,我们将其命名为 <curl>。该文件夹中包含一个名为 [curl.exe] 的可执行文件。这将是我们用于查询 Web 服务器的客户端。打开命令提示符窗口,并导航至 <curl> 文件夹:
dos>dir curl.exe
22/03/2004 13:29 299 008 curl.exe
E:\curl2>curl
curl: try 'curl --help' for more information
dos>curl --help | more
Usage: curl [options...] <url>
Options: (H) means HTTP/HTTPS only, (F) means FTP only
-a/--append Append to target file when uploading (F)
-A/--user-agent <string> User-Agent to send to server (H)
--anyauth Tell curl to choose authentication method (H)
-b/--cookie <name=string/file> Cookie string or file to read cookies from (H)
--basic Enable HTTP Basic Authentication (H)
-B/--use-ascii Use ASCII/text transfer
-c/--cookie-jar <file> Write cookies to this file after operation (H)
....
让我们使用此应用程序查询一个 Web 服务器,并检查客户端与服务器之间的交互。我们将处于以下情况:

Web 服务器可以是任意服务器。此处,我们的目标是发现 curl Web 客户端与 Web 服务器之间将发生的交互。此前,我们创建了以下静态 HTML 页面:
<html>
<head>
<title>essai 1 : une page statique</title>
</head>
<body>
<center>
<h1>Une page statique...</h1>
</body>
</html>
我们在浏览器中查看它:

我们可以看到请求的 URL 是:http://localhost/aspnet/chap1/statique1.html。因此,Web 服务器是端口 80 上的 localhost(即本地机器)。如果查看此网页的 HTML 源代码(查看/源代码),我们会看到最初创建的 HTML 文本:

现在让我们使用 CURL 客户端请求同一个 URL:
dos>curl http://localhost/aspnet/chap1/statique1.html
<html>
<head>
<title>essai 1 : une page statique</title>
</head>
<body>
<center>
<h1>Une page statique...</h1>
</body>
</html>
我们可以看到,Web 服务器向其发送了一组文本行,这些文本行代表了所请求页面的 HTML 代码。我们之前提到过,Web 服务器的响应采用以下形式:

然而,这里我们没有看到 HTTP 头部。这是因为 [curl] 默认不显示它们。--include 选项允许你显示它们:
E:\curl2>curl --include http://localhost/aspnet/chap1/statique1.html
HTTP/1.1 200 OK
Server: Microsoft ASP.NET Web Matrix Server/0.6.0.0
Date: Mon, 22 Mar 2004 16:51:00 GMT
X-AspNet-Version: 1.1.4322
Cache-Control: public
ETag: "1C4102CEE8C6400:1C4102CFBBE2250"
Content-Type: text/html
Content-Length: 161
Connection: Close
<html>
<head>
<title>essai 1 : une page statique</title>
</head>
<body>
<center>
<h1>Une page statique...</h1>
</body>
</html>
服务器确实发送了一系列HTTP头部,后面紧跟一个空行:
HTTP/1.1 200 OK
Server: Microsoft ASP.NET Web Matrix Server/0.6.0.0
Date: Mon, 22 Mar 2004 16:51:00 GMT
X-AspNet-Version: 1.1.4322
Cache-Control: public
ETag: "1C4102CEE8C6400:1C4102CFBBE2250"
Content-Type: text/html
Content-Length: 161
Connection: Close
服务器表示
| |
服务器进行自我标识。此处为 Cassini 服务器 | |
响应的日期/时间 | |
Cassini 服务器特有的标头 | |
向客户端提供有关所发送响应是否可缓存的信息。[public] 属性告知客户端可以缓存该页面。若为 [no-cache] 属性,则会告知客户端不要缓存该页面。 | |
... | |
服务器表明将发送 HTML 格式的文本。 | |
这是在HTTP头部之后将发送的文档的字节数。该数值实际上是文件essai1.html的字节大小: | |
服务器表明文档发送完成后将关闭连接 |
客户端接收到这些 HTTP 头,现在知道将收到代表 HTML 文档的 161 字节数据。服务器在表示 HTTP 头结束的空行之后立即发送这 161 字节:
<html>
<head>
<title>essai 1 : une page statique</title>
</head>
<body>
<center>
<h1>Une page statique...</h1>
</body>
</html>
这里我们看到了HTML文件的原始结构。如果我们的客户端是一个浏览器,在收到这些文本行后,它会将其解析并向用户显示以下页面:

让我们再次使用 [curl] 客户端请求同一资源,但这次只获取响应头:
dos>curl --head http://localhost/aspnet/chap1/statique1.html
HTTP/1.1 200 OK
Server: Microsoft ASP.NET Web Matrix Server/0.6.0.0
Date: Tue, 23 Mar 2004 07:11:54 GMT
Cache-Control: public
ETag: "1C410A504D60680:1C410A58621AD3E"
Content-Type: text/html
Content-Length: 161
Connection: Close
没有 HTML 文档时,我们得到的结果与之前相同。现在让我们使用浏览器和通用 TCP 客户端来请求一张图片。首先,使用浏览器:

文件 univ01.gif 大小为 4052 字节:
现在让我们使用 [curl] 客户端:
dos>curl --head http://localhost/aspnet/chap1/univ01.gif
HTTP/1.1 200 OK
Server: Microsoft ASP.NET Web Matrix Server/0.6.0.0
Date: Tue, 23 Mar 2004 07:18:44 GMT
Cache-Control: public
ETag: "1C410A6795D7500:1C410A6868B1476"
Content-Type: image/gif
Content-Length: 4052
Connection: Close
请注意上述请求-响应循环中的以下几点:
| |
| |
|
2.6.3.2. HTTP 客户端的请求
现在,让我们思考以下问题:如果我们要编写一个能与 Web 服务器“对话”的程序,它必须向 Web 服务器发送哪些命令才能获取给定的资源?在之前的示例中,我们看到了客户端接收的内容,但未看到客户端发送的内容。我们将使用 curl 的 [--verbose] 选项,同时查看客户端向服务器发送的内容。让我们从请求静态页面开始:
dos>curl --verbose http://localhost/aspnet/chap1/statique1.html
* About to connect() to localhost:80
* Connected to portable1_tahe (127.0.0.1) port 80
> GET /aspnet/chap1/statique1.html HTTP/1.1
User-Agent: curl/7.10.8 (win32) libcurl/7.10.8 OpenSSL/0.9.7a zlib/1.1.4
Host: localhost
Pragma: no-cache
Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, */*
< HTTP/1.1 200 OK
< Server: Microsoft ASP.NET Web Matrix Server/0.6.0.0
< Date: Tue, 23 Mar 2004 07:37:06 GMT
< Cache-Control: public
< ETag: "1C410A504D60680:1C410A58621AD3E"
< Content-Type: text/html
< Content-Length: 161
< Connection: Close
<html>
<head>
<title>essai 1 : une page statique</title>
</head>
<body>
<center>
<h1>Une page statique...</h1>
</body>
</html>
* Closing connection #0
首先,[curl] 客户端与本地主机(=127.0.0.1)的 80 端口建立 TCP/IP 连接
建立连接后,它会发送 HTTP 请求。这是一个以空行结尾的文本行序列:
GET /aspnet/chap1/statique1.html HTTP/1.1
User-Agent: curl/7.10.8 (win32) libcurl/7.10.8 OpenSSL/0.9.7a zlib/1.1.4
Host: localhost
Pragma: no-cache
Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, */*
Web 客户端的 HTTP 请求有两个功能:
- 指定所需的资源。这是第一行的作用,即 GET
- 提供关于发起请求的客户端的信息,以便服务器可能针对这种特定类型的客户端定制其响应。
客户端 [curl] 发送的上述行含义如下:
使用特定版本的 HTTP 协议请求指定资源。服务器会发送 HTTP 格式的响应,随后是一行空行,接着是所请求的资源 | |
用于标识客户端身份 | |
用于指定(HTTP 1.1 协议)被查询 Web 服务器的机器和端口 | |
用于指定客户端不支持缓存。 | |
MIME 类型,指定客户端可处理的文件类型 |
让我们使用 [curl] 的 --head 选项再次尝试该操作:
dos>curl --verbose --head --output reponse.txt http://localhost/aspnet/chap1/statique1.html
* About to connect() to localhost:80
* Connected to portable1_tahe (127.0.0.1) port 80
> HEAD /aspnet/chap1/statique1.html HTTP/1.1
User-Agent: curl/7.10.8 (win32) libcurl/7.10.8 OpenSSL/0.9.7a zlib/1.1.4
Host: localhost
Pragma: no-cache
Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, */*
< HTTP/1.1 200 OK
< Server: Microsoft ASP.NET Web Matrix Server/0.6.0.0
< Date: Tue, 23 Mar 2004 07:54:22 GMT
< Cache-Control: public
< ETag: "1C410A504D60680:1C410A58621AD3E"
< Content-Type: text/html
< Content-Length: 161
< Connection: Close
我们将仅关注客户端发送的 HTTP 头:
HEAD /aspnet/chap1/statique1.html HTTP/1.1
User-Agent: curl/7.10.8 (win32) libcurl/7.10.8 OpenSSL/0.9.7a zlib/1.1.4
Host: localhost
Pragma: no-cache
Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, */*
只有请求资源的命令发生了变化。现在,我们使用的是 HEAD 命令,而不是 GET 命令。该命令要求服务器的响应仅限于 HTTP 头,且不发送所请求的资源。上图的屏幕截图未显示接收到的 HTTP 头。这些头信息已通过 [curl] 命令的 [--output response.txt] 选项保存到文件中:
dos>more reponse.txt
HTTP/1.1 200 OK
Server: Microsoft ASP.NET Web Matrix Server/0.6.0.0
Date: Tue, 23 Mar 2004 07:54:22 GMT
Cache-Control: public
ETag: "1C410A504D60680:1C410A58621AD3E"
Content-Type: text/html
Content-Length: 161
Connection: Close
2.6.4. 结论
我们通过几个示例考察了 Web 客户端请求的结构以及 Web 服务器对此的响应。通信通过 HTTP 协议进行,该协议是一组双方之间交换的基于文本的命令。客户端的请求和服务器的响应具有以下共同结构:

在客户端请求(通常称为查询)的情况下,[Document] 部分通常不存在。不过,客户端也可以向服务器发送文档,此时会使用名为 PUT 的命令。请求资源的两个常用命令是 GET 和 POST,后者将在稍后讨论。 HEAD命令仅用于请求HTTP头部信息。GET和POST命令是基于浏览器的Web客户端最常用的命令。
作为对客户端请求的响应,服务器会发送结构相同的响应。所请求的资源将通过 [Document] 部分传输,除非客户端发出的命令是 HEAD,这种情况下仅发送 HTTP 头部。
2.7. HTML
Web 浏览器可以显示各种文档,其中最常见的是 HTML(超文本标记语言)文档。这是使用 <tag>text</tag> 形式的标签进行格式化的文本。因此,文本 <B>important</B> 将以粗体显示“important”。还有一些独立的标签,例如 标签,它会显示一条水平线。我们不会逐一介绍 HTML 文本中所有可用的标签。 有许多所见即所得(WYSIWYG)软件,允许您无需编写任何 HTML 代码即可构建网页。这些工具会自动为使用鼠标和预定义控件创建的布局生成 HTML 代码。因此,您可以(使用鼠标)将表格插入页面,然后查看软件生成的 HTML 代码,从而了解在网页上定义表格应使用的标签。就是这么简单。 此外,掌握 HTML 知识至关重要,因为动态 Web 应用程序必须自行生成 HTML 代码并发送给 Web 客户端。该代码是通过编程生成的,而您当然必须清楚该生成什么内容,才能确保客户端收到他们想要的网页。
简而言之,您无需精通整个HTML语言即可开始进行网页编程。不过,这方面的知识是必要的,您可以通过使用Word、FrontPage、DreamWeaver等数十种所见即所得(WYSIWYG)网页编辑器来掌握。探索HTML奥秘的另一种方法是浏览网页,查看那些包含您未曾见过的有趣元素的页面源代码。
2.7.1. 示例
请看以下示例,该示例使用FrontPage Express(Internet Explorer自带的免费工具)创建。此处已对FrontPage生成的代码进行了简化。该示例包含网页文档中常见的一些元素,例如:
- 一个表格
- 一张图片
- 一个链接

HTML文档通常具有以下形式:
整个文档被包含在 <html>...</html> 标签之间。它由两部分组成:
- <head>...</head>:这是文档中不可显示的部分。它向将显示该文档的浏览器提供信息。该部分通常包含 <title>...</title> 标签,用于设置将在浏览器标题栏中显示的文本。 该部分还可能包含其他标签,包括定义文档关键词的标签,这些关键词随后会被搜索引擎使用。该部分还可能包含脚本,通常采用 JavaScript 或 VBScript 编写,这些脚本将由浏览器执行。
- <body attributes>...</body>:这是浏览器将显示的部分。该部分包含的 HTML 标签向浏览器说明了文档的“预期”视觉布局。每种浏览器对这些标签的解释方式各不相同。因此,两款浏览器可能以不同的方式显示同一个网页文档。这通常是网页设计师面临的挑战之一。
本示例文档的 HTML 代码如下:
<html>
<head>
<title>balises</title>
</head>
<body background="/images/standard.jpg">
<center>
<h1>Les balises HTML</h1>
<hr>
</center>
<table border="1">
<tr>
<td>cellule(1,1)</td>
<td valign="middle" align="center" width="150">cellule(1,2)</td>
<td>cellule(1,3)</td>
</tr>
<tr>
<td>cellule(2,1)</td>
<td>cellule(2,2)</td>
<td>cellule(2,3</td>
</tr>
</table>
<table border="0">
<tr>
<td>Une image</td>
<td><img border="0" src="/images/univ01.gif" width="80" height="95"></td>
</tr>
<tr>
<td>le site de l'ISTIA</td>
<td><a href="http://istia.univ-angers.fr">ici</a></td>
</tr>
</table>
</body>
</html>
代码中仅突出显示了我们感兴趣的部分:
HTML | HTML 标签及示例 |
<title>标签</title> 文档显示时,该文本将出现在浏览器的标题栏中 | |
<horizontal>:显示一条水平线 | |
<table attributes>....</table> : 用于定义表格 <tr attributes>... : 用于定义一行 <td 属性>... : 定义一个单元格 示例: <table border="1">... : border 属性定义表格边框的粗细 <td valign="middle" align="center" width="150">单元格(1,2) : 定义一个内容为单元格(1,2)的单元格。该内容将垂直居中(valign="middle")和水平居中(align="center")。该单元格的宽度为 150 像素(width="150") | |
<img border="0" src="/images/univ01.gif" width="80" height="95"> : 定义了一张无边框(border="0")的图片, 高度为 95 像素(height="95"),宽度为 80 像素(width="80"),其源文件位于 Web 服务器上的 /images/univ01.gif(src="/images/univ01.gif")。该链接位于可通过 URL http://localhost:81/html/balises.htm 访问的 Web 文档中。 因此,浏览器将请求 URL http://localhost:81/images/univ01.gif 以获取此处引用的图像。 | |
<a href="http://istia.univ-angers.fr">here: 使文本“here”作为指向 URL http://istia.univ-angers.fr 的链接。 | |
<body background="/images/standard.jpg">:表示用作页面背景的图像位于 Web 服务器上的 URL /images/standard.jpg。在本例中,浏览器将请求 URL http://localhost:81/images/standard.jpg 以检索此背景图像。 |
在这个简单示例中,我们可以看到,为了构建整个文档,浏览器必须向服务器发出三个请求:
- http://localhost:81/html/balises.htm 以获取文档的 HTML 源代码
- http://localhost:81/images/univ01.gif 用于获取图片 univ01.gif
- http://localhost:81/images/standard.jpg 用于获取背景图片 standard.jpg
以下示例展示了一个同样使用 FrontPage 创建的 Web 表单。

FrontPage 生成的 HTML 代码经过轻微整理后如下所示:
<html>
<head>
<title>balises</title>
<script language="JavaScript">
function effacer(){
alert("Vous avez cliqué sur le bouton Effacer");
}//effacer
</script>
</head>
<body background="/images/standard.jpg">
<form method="POST" >
<table border="0">
<tr>
<td>Etes-vous marié(e)</td>
<td>
<input type="radio" value="Oui" name="R1">Oui
<input type="radio" name="R1" value="non" checked>Non
</td>
</tr>
<tr>
<td>Cases à cocher</td>
<td>
<input type="checkbox" name="C1" value="un">1
<input type="checkbox" name="C2" value="deux" checked>2
<input type="checkbox" name="C3" value="trois">3
</td>
</tr>
<tr>
<td>Champ de saisie</td>
<td>
<input type="text" name="txtSaisie" size="20" value="qqs mots">
</td>
</tr>
<tr>
<td>Mot de passe</td>
<td>
<input type="password" name="txtMdp" size="20" value="unMotDePasse">
</td>
</tr>
<tr>
<td>Boîte de saisie</td>
<td>
<textarea rows="2" name="areaSaisie" cols="20">
ligne1
ligne2
ligne3
</textarea>
</td>
</tr>
<tr>
<td>combo</td>
<td>
<select size="1" name="cmbValeurs">
<option>choix1</option>
<option selected>choix2</option>
<option>choix3</option>
</select>
</td>
</tr>
<tr>
<td>liste à choix simple</td>
<td>
<select size="3" name="lst1">
<option selected>liste1</option>
<option>liste2</option>
<option>liste3</option>
<option>liste4</option>
<option>liste5</option>
</select>
</td>
</tr>
<tr>
<td>liste à choix multiple</td>
<td>
<select size="3" name="lst2" multiple>
<option>liste1</option>
<option>liste2</option>
<option selected>liste3</option>
<option>liste4</option>
<option>liste5</option>
</select>
</td>
</tr>
<tr>
<td>bouton</td>
<td>
<input type="button" value="Effacer" name="cmdEffacer" onclick="effacer()">
</td>
</tr>
<tr>
<td>envoyer</td>
<td>
<input type="submit" value="Envoyer" name="cmdRenvoyer">
</td>
</tr>
<tr>
<td>rétablir</td>
<td>
<input type="reset" value="Rétablir" name="cmdRétablir">
</td>
</tr>
</table>
<input type="hidden" name="secret" value="uneValeur">
</form>
</body>
</html>
视觉元素与 HTML 标签的映射关系如下:
可视化控件 | HTML标签 |
<form method="POST" > | |
<input type="text" name="txtInput" size="20" value="a few words"> | |
<input type="password" name="txtPassword" size="20" value="aPassword"> | |
<textarea rows="2" name="inputArea" cols="20"> 第1行 第2行 第3行 </textarea> | |
<input type="radio" value="是" name="R1">是 <input type="radio" name="R1" value="No" checked>否 | |
<input type="checkbox" name="C1" value="one">1 <input type="checkbox" name="C2" value="two" checked>2 <input type="checkbox" name="C3" value="three">3 | |
<select size="1" name="cmbValues"> <option>选项1</option> <option selected>选项2</option> <option>选项3</option> </select> | |
<select size="3" name="lst1"> <option selected>list1</option> <option>列表2</option> <option>列表3</option> <option>列表4</option> <option>列表5</option> </select> | |
<select size="3" name="lst2" multiple> <option>列表1</option> <option>list2</option> <option selected>列表3</option> <option>列表4</option> <option>列表5</option> </select> | |
<input type="submit" value="提交" name="cmdSubmit"> | |
<input type="reset" value="重置" name="cmdReset"> | |
<input type="button" value="清除" name="cmdClear" onclick="clear()"> |
让我们回顾一下这些不同的控件。
2.7.1.1. 表单
<form method="POST" > |
<form name="..." method="..." action="...">...</form> | |
name="exampleform":表单名称 method="...":浏览器用于将表单中收集的值发送至 Web 服务器的方法 action="...":表单中收集的值将被发送到的 URL。 Web表单由<form>...</form>标签包围。表单可以指定名称(name="xx")。这适用于表单中的所有控件。如果Web文档中包含需要引用表单元素的脚本,该名称便十分有用。表单的目的是收集用户通过键盘或鼠标输入的信息,并将其发送至某个Web服务器URL。是哪个URL? 即 action="URL" 属性中指定的那个。如果缺少该属性,信息将发送至包含该表单的文档的 URL。上文示例中就是这种情况。到目前为止,我们一直将 Web 客户端视为向 Web 服务器“请求”信息,从未将其视为向服务器“提供”信息。Web 客户端如何将信息(表单中的数据)提供给 Web 服务器? 稍后我们将详细讨论这一点。它可以使用两种不同的方法:POST 和 GET。<form> 标签中的 method="method" 属性(其中 method 设置为 GET 或 POST)会告诉浏览器,应使用哪种方法将表单中收集的信息发送至 action="URL" 属性指定的 URL。当未指定 method 属性时,默认使用 GET 方法。 |
2.7.1.2. 输入字段
![]()
![]()
<input type="text" name="txtInput" size="20" value="some words"> <input type="password" name="txtMdp" size="20" value="aPassword"> |
<input type="..." name="..." size=".." value=".."> input 标签适用于各种控件。正是 type 属性将这些不同的控件区分开来。 | |
type="text":指定这是一个文本输入框 type="password":输入字段中的字符将被星号(*)替换。这是它与普通输入字段的唯一区别。此类控件适用于输入密码。 size="20":字段中可见的字符数——不会阻止输入更多字符 name="txtInput":控件的名称 value="some words":将在输入框中显示的文本。 |
2.7.1.3. 多行输入字段
![]()
<textarea rows="2" name="areaSaisie" cols="20"> 第1行 第2行 第3行 </textarea> |
<textarea ...>文本</textarea> 显示一个包含初始文本的多行文本输入框 | |
rows="2": 行数 cols="'20":列数 name="areaSaisie":控件名称 |
2.7.1.4. 单选按钮
![]()
<input type="radio" value="Yes" name="R1">是 <input type="radio" name="R1" value="no" checked>否 |
<input type="radio" attribute2="value2" ....>文本 显示一个旁边带有文本的单选按钮。 | |
name="radio":控件的名称。名称相同的单选按钮构成一组互斥按钮:其中只能选中一个。 value="value":分配给单选按钮的值。请勿将此值与单选按钮旁显示的文本混淆。该文本仅用于显示目的。 checked:如果存在此关键字,则单选按钮被选中;否则,则未被选中。 |
2.7.1.5. 复选框
<input type="checkbox" name="C1" value="one">1 <input type="checkbox" name="C2" value="two" checked>2 <input type="checkbox" name="C3" value="three">3 |
![]()
<input type="checkbox" attribute2="value2" ....>文本 显示一个复选框,旁边带有文本。 | |
name="C1":控件的名称。复选框可以具有相同的名称,也可以不具有相同的名称。具有相同名称的复选框构成一组关联的复选框。 value="value":分配给复选框的值。请勿将此值与复选框旁显示的文本混淆。该文本仅用于显示目的。 已选中:如果存在此关键字,则单选按钮处于选中状态;否则,则未选中。 |
2.7.1.6. 下拉列表(组合框)
<select size="1" name="cmbValues"> <option>选项1</option> <option selected>选项2</option> <option>选项3</option> </select> |
![]()
<select size=".." name=".."> <option [selected]>...</option> ... </select> 在列表中显示 <option>...</option> 标签之间的文本 | |
name="cmbValues":控件的名称。 size="1":可见列表项的数量。size="1" 使列表等同于一个下拉列表框。 selected:如果列表项具有此属性,则该项在列表中显示为已选中。在上例中,列表项“choice2”在组合框首次显示时作为已选中项出现。 |
2.7.1.7. 单选列表
<select size="3" name="lst1"> <option selected>list1</option> <option>list2</option> <option>列表3</option> <option>列表4</option> <option>列表5</option> </select> |

<select size=".." name=".."> <option [selected]>...</option> ... </select> 在列表中显示 <option>...</option> 标签之间的文本 | |
与仅显示一个项的下拉列表相同。该控件与前面的下拉列表唯一的区别在于其 size>1 属性。 |
2.7.1.8. 多选列表
<select size="3" name="lst2" multiple> <option selected>list1</option> <option>list2</option> <option selected>列表3</option> <option>列表4</option> <option>列表5</option> </select> |

<select size=".." name=".." multiple> <option [selected]>...</option> ... </select> 在列表中显示 <option>...</option> 标签之间的文本 | |
multiple:允许从列表中选择多个项目。在上例中,list1 和 list3 均被选中。 |
2.7.1.9. 按钮
<input type="button" value="清除" name="cmdClear" onclick="clear()"> |
![]()
<input type="button" value="..." name="..." onclick="clear()" ....> | |
type="button":定义一个按钮控件。还有另外两种类型的按钮:submit 和 reset。 value="清除":按钮上显示的文本 onclick="function()":允许您定义一个函数,当用户点击按钮时执行该函数。该函数是显示的网页文档中定义的脚本的一部分。上述语法是 JavaScript 语法。如果脚本使用 VBScript 编写,则应写为 onclick="function",不带圆括号。 如果需要向函数传递参数,语法保持不变:onclick="function(val1, val2,...)" 在本例中,点击“清除”按钮将调用以下 JavaScript clear 函数:
clear 函数会显示一条消息: ![]() |
2.7.1.10. 提交按钮
<input type="submit" value="发送" name="cmdSend"> |
![]()
<input type="submit" value="发送" name="cmdRenvoyer"> | |
type="submit":将该按钮定义为向 Web 服务器发送表单数据的按钮。当用户点击此按钮时,浏览器将使用该标签的 method 属性所定义的方法,将表单数据发送至 <form> 标签的 action 属性中定义的 URL。 value="提交":按钮上显示的文本 |
2.7.1.11. 重置按钮
<input type="reset" value="重置" name="cmdReset"> |
![]()
<input type="reset" value="重置" name="cmdReset"> | |
type="reset":将该按钮定义为表单重置按钮。当用户点击此按钮时,浏览器将把表单恢复到接收时的状态。 value="重置":按钮上显示的文本 |
2.7.1.12. 隐藏字段
<input type="hidden" name="secret" value="aValue"> |
<input type="hidden" name="..." value="..."> | |
type="hidden":指定这是一个隐藏字段。隐藏字段是表单的一部分,但不会显示给用户。不过,如果用户在浏览器中查看源代码,他们会看到 <input type="hidden" value="..."> 标签,从而看到隐藏字段的值。 value="aValue":隐藏字段的值。 隐藏字段的用途是什么?它允许 Web 服务器在客户端的多次请求中保留信息。以一个在线购物应用程序为例。客户在商品目录的第一页购买了第一件商品 art1,数量为 q1,然后转到目录中的新页面。为了记住客户购买了 q1 件 art1,服务器可以将这两条信息放入新页面 Web 表单中的一个隐藏字段中。 在此新页面上,客户端购买了 q2 件商品 art2。当第二个表单的数据提交至服务器时,服务器不仅会收到 (q2,art2) 信息,还会收到 (q1,art1) 信息——后者作为表单中的隐藏字段,用户无法对其进行修改。 随后,Web 服务器将把信息 (q1,art1) 和 (q2,art2) 放入一个新的隐藏字段中,并发送一个新的目录页面。以此类推。 |
2.7.2. Web客户端向Web服务器发送表单值
我们在前一节中提到,Web 客户端有两种方法将显示的表单值发送给 Web 服务器:GET 和 POST 方法。 让我们通过一个示例来看看这两种方法的区别。前面讨论的页面是一个静态页面。为了访问请求该文档的浏览器发送的 HTTP 头,我们将把它转换为一个面向 .NET Web 服务器(IIS 或 Cassini)的动态页面。这里重点不在于 .NET 技术——这将在下一章中介绍——而在于客户端与服务器的通信。ASP.NET 页面的代码如下:
<%@ Page Language="vb" CodeBehind="params.aspx.vb" AutoEventWireup="false" Inherits="ConsoleApplication1.params" %>
<script runat="server">
Private Sub Page_Init(Byval Sender as Object, Byval e as System.EventArgs)
' save the query
saveRequest
end sub
Private Sub saveRequest
' saves the current query in request.txt of the page folder
dim requestFileName as String=Me.MapPath(Me.TemplateSourceDirectory)+"\request.txt"
Me.Request.SaveAs(requestFileName,true)
end sub
</script>
<html>
<head>
<title>balises</title>
<script language="JavaScript">
function effacer(){
alert("Vous avez cliqué sur le bouton Effacer");
}//effacer
</script>
</head>
<body background="/images/standard.jpg">
....
</body>
</html>
在当前页面的 HTML 内容中,我们添加了一段 VB.NET 代码。我们不会对这段代码进行详细说明,仅需说明的是:每当调用上述文档时,Web 服务器会将 Web 客户端的请求保存到被调用文档所在文件夹中的 [request.txt] 文件中。
2.7.2.1. GET 方法
让我们进行一个初步测试,在文档的 HTML 代码中,FORM 标签定义如下:
<form method="get">
上述文档(HTML+VB代码)命名为 [params.aspx]。它位于 .NET Web 服务器(IIS/Cassini)的目录树中,可通过 URL http://localhost/aspnet/chap1/params.aspx 访问:

浏览器刚刚发出请求,我们知道该请求已保存到文件 [request.txt] 中。让我们查看其内容:
GET /aspnet/chap1/params.aspx HTTP/1.1
Connection: keep-alive
Keep-Alive: 300
Accept: application/x-shockwave-flash,text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,image/jpeg,image/gif;q=0.2,*/*;q=0.1
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Accept-Encoding: gzip,deflate
Accept-Language: en-us,en;q=0.5
Host: localhost
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.6) Gecko/20040113
我们看到了在 [curl] 客户端中已经遇到过的元素。还有一些是首次出现的:
客户端请求服务器在响应后不要关闭连接。这将允许它在后续请求中使用同一连接。连接不会无限期保持打开状态。如果长时间无活动,服务器将关闭它。 | |
[Keep-Alive] 连接保持打开状态的时长(以秒为单位) | |
客户端支持的字符集 | |
客户端首选的语言列表。 |
我们按以下方式填写表单:

我们使用上方的 [提交] 按钮。其 HTML 代码如下:
当点击 [提交] 按钮时,浏览器会将表单参数(即 <form> 标签)发送至 <form action="URL"> 标签中 [action] 属性指定的 URL(如果该属性存在)。如果该属性不存在,表单参数将发送至提供该表单的 URL。本例即属于这种情况。 因此,[提交]按钮应触发浏览器向URL [http://localhost/aspnet/chap1/params.aspx] 发送包含表单参数的请求。由于[params.aspx]页面存储了接收到的请求,我们应该能够确定客户端是如何提交这些参数的。让我们试一试。我们点击[提交]按钮。我们从浏览器收到以下响应:

这是初始页面,但您可以看到浏览器 [地址] 栏中的 URL 已发生变化,变为:
http://localhost/aspnet/chap1/params.aspx?R1=Oui&C1=un&C2=deux&txtSaisie=programmation+web&txtMdp=ceciestsecret&areaSaisie=les+bases+de+la%0D%0Aprogrammation+web&cmbValeurs=choix3&lst1=liste3&lst2=liste1&lst2=liste3&cmdRenvoyer=Envoyer&secret=uneValeur
我们可以看到,表单中的选择反映在了 URL 中。让我们来看一下 [request.txt] 文件的内容,该文件存储了客户端的请求:
GET /aspnet/chap1/params.aspx?R1=Oui&C1=un&C2=deux&txtSaisie=programmation+web&txtMdp=ceciestsecret&areaSaisie=les+bases+de+la%0D%0Aprogrammation+web&cmbValeurs=choix3&lst1=liste3&lst2=liste1&lst2=liste3&cmdRenvoyer=Envoyer&secret=uneValeur HTTP/1.1
Connection: keep-alive
Keep-Alive: 300
Accept: application/x-shockwave-flash,text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,image/jpeg,image/gif;q=0.2,*/*;q=0.1
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Accept-Encoding: gzip,deflate
Accept-Language: en-us,en;q=0.5
Host: localhost
Referer: http://localhost/aspnet/chap1/params.aspx
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.6) Gecko/20040113
我们看到一个与浏览器最初请求文档时(未发送任何参数)发出的请求非常相似的 HTTP 请求。有两个区别:
表单参数已通过 ?param1=val1¶m2=val2&... 的形式附加到文档 URL 上 | |
客户端使用此 HTTP 标头来指示发起请求时正在显示的文档的 URL |
让我们仔细看看参数是如何在 GET URL?param1=value1¶m2=value2&... HTTP/1.1 请求中传递的,其中 param1、param2 等是 Web 表单控件的名称,value1、value2 等是与它们关联的值。下面是一个三列表格:
- 第 1 列:展示示例中 HTML 控件的定义
- 第 2 列:展示该控件在浏览器中的显示效果
- 第 3 列:展示浏览器针对第 1 列中的控件向服务器发送的值,采用示例中 GET 请求的格式
HTML 控件 | 可视化 | 返回值 |
<input type="radio" value="Yes" name="R1">是 <input type="radio" name="R1" value="no" checked>No | - 用户所选单选按钮的 value 属性的值。 | |
<input type="checkbox" name="C1" value="one">1 <input type="checkbox" name="C2" value="two" checked>2 <input type="checkbox" name="C3" value="three">3 | C1=one C2=two - 用户选中的复选框的“value”属性的值 | |
<input type="text" name="txtInput" size="20" value="a few words"> | txtInput=web+programming - 用户在输入框中输入的文本。空格已被 + 号替换 | |
<input type="password" name="txtMdp" size="20" value="aPassword"> | txtPassword=thisIsSecret - 用户在输入框中输入的文本 | |
<textarea rows="2" name="inputArea" cols="20"> line1 第2行 第3行 </textarea> | areaSaisie=Web+编程+基础%0D%0A 网页编程 - 用户在输入框中输入的文本。%OD%OA 是换行标记。空格已被 + 号替换 | |
<select size="1" name="cmbValues"> <option>选项1</option> <option selected>选项2</option> <option>选项3</option> </select> | cmbValues=option3 - 用户从单选列表中选定的值 | |
<select size="3" name="lst1"> <option selected>list1</option> <option>list2</option> <option>列表3</option> <option>列表4</option> <option>列表5</option> </select> | ![]() | lst1=list3 - 用户从单选列表中选择的值 |
<select size="3" name="lst2" multiple> <option selected>list1</option> <option>list2</option> <option selected>list3</option> <option>列表4</option> <option>列表5</option> </select> | ![]() | lst2=list1 lst2=list3 - 用户从多选列表中选定的值 |
<input type="submit" value="提交" name="cmdSubmit"> | cmdSubmit=提交 - 用于将表单数据发送至服务器的按钮的 name 和 value 属性 | |
<input type="hidden" name="secret" value="aValue"> | secret=aValue - 隐藏字段的 value 属性 |
您可能会好奇,服务器对传入的参数做了什么处理。实际上,什么也没做。在收到请求后
GET /aspnet/chap1/params.aspx?R1=Oui&C1=un&C2=deux&txtSaisie=programmation+web&txtMdp=ceciestsecret&areaSaisie=les+bases+de+la%0D%0Aprogrammation+web&cmbValeurs=choix3&lst1=liste3&lst2=liste1&lst2=liste3&cmdRenvoyer=Envoyer&secret=uneValeur HTTP/1.1
Web 服务器已将 URL 中的参数传递给了文档 http://localhost/aspnet/chap1/params.aspx,即我们最初创建的文档。我们尚未编写任何代码来检索和处理客户端发送给我们的参数。因此,一切都发生得仿佛客户端的请求仅仅是:
这就是为什么,当我们点击 [Submit] 按钮时,收到的页面与最初通过请求不带参数的 URL [http://localhost/aspnet/chap1/params.aspx] 所获得的页面完全相同。
2.7.2.2. POST 方法
现在,HTML 文档已配置为让浏览器使用 POST 方法将表单值发送给 Web 服务器:
我们通过 URL [http://localhost/aspnet/chap1/params.aspx] 请求新文档,像使用 GET 方法时那样填写表单,并使用 [提交] 按钮将参数提交给服务器。我们从服务器收到以下响应页面:

因此,我们得到的结果与 GET 方法相同,即初始页面。请注意一个区别:在浏览器的 [地址] 栏中,传输的参数不会显示。现在,让我们查看客户端发送并保存在 [request.txt] 文件中的请求:
POST /aspnet/chap1/params.aspx HTTP/1.1
Connection: keep-alive
Keep-Alive: 300
Content-Length: 210
Content-Type: application/x-www-form-urlencoded
Accept: application/x-shockwave-flash,text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,image/jpeg,image/gif;q=0.2,*/*;q=0.1
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Accept-Encoding: gzip,deflate
Accept-Language: en-us,en;q=0.5
Host: localhost
Referer: http://localhost/aspnet/chap1/params.aspx
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.6) Gecko/20040113
R1=Oui&C1=un&C2=deux&txtSaisie=programmation+web&txtMdp=ceciestsecrey&areaSaisie=les+bases+de+la%0D%0Aprogrammation+web&cmbValeurs=choix3&lst1=liste3&lst2=liste1&lst2=liste3&cmdRenvoyer=Envoyer&secret=uneValeur
客户端的 HTTP 请求中出现了新元素:
GET请求已被POST请求取代。参数不再出现在请求的第一行。我们可以看到,它们现在被放置在HTTP请求之后,紧随一个空行。其编码与GET请求中的编码完全一致。 | |
“提交”的字符数,即 Web 服务器在接收 HTTP 头部后,为检索客户端发送的文档必须读取的字符数。此处的文档即表单值的列表。 | |
指定客户端在 HTTP 头部之后将发送的文档类型。类型 [application/x-www-form-urlencoded] 表示这是一个包含表单值的文档。 |
向 Web 服务器传输数据有两种方法:GET 和 POST。哪种方法更好?我们已经看到,如果浏览器使用 GET 方法发送表单值,浏览器会在地址栏中显示请求的 URL,格式为 URL?param1=val1¶m2=val2&.... 这既可以视为优点,也可以视为缺点:
- 如果你希望允许用户将这个带参数的 URL 保存到书签中,这便是一个优势
- 若不希望用户访问某些表单信息(如隐藏字段),则属于缺点
从现在起,我们在表单中将几乎完全使用 POST 方法。
2.8. 结论
本章介绍了 Web 开发的各种基本概念:
- 可用的各种工具和技术(Java、ASP、ASP.NET、PHP、Perl、VBScript、JavaScript)
- 通过 HTTP 协议进行的客户端-服务器通信
- 使用 HTML 设计文档
- 输入表单的设计
我们通过一个示例了解了客户端如何向 Web 服务器发送信息。但我们尚未涉及服务器如何
- 检索这些信息
- 处理这些信息
- 根据处理结果向客户端发送动态响应
这属于 Web 编程的范畴,我们将在下一章介绍 ASP.NET 技术时探讨这一主题。


