成都教育学院 傅叔平
五.环境变量
六.URL解码
七.返回信息到客户端
八.应用实例
如果仅仅为了浏览网页,我们并不需要CGI(Common Gateway Interface)。CGI的作用是构成Web的交互性,当用户在网页的表单中提交的数据传送到Web服务器时,就需要CGI来处理这些数据了。CGI在Internet上广泛应用,如用户注册、网上调查、留言板、网上购物等等需要处理用户提交的数据的场合。
由于在校园网中同样地使用了 Internet的通信标准和工具,一样地使用ICP/IP协议,所以,只要作适当的设置,在校园网上也可作各种Internet的应用。在校园网中,CGI同样大有用武之地。只要在校园网上构筑一台Web服务器,Internet上所有CGI的应用都可以搬到校园网中来,如网上报名、网上调查、查询图书资料、校园网内聊天、校园留言板等。
CGI的主要作用是在WWW环境下,由Web服务器启动相关的程序代码来处理用户从客户端传送来的数据,当用户在网页上填写了表单,并点击“提交”(submit)按钮后,客户端用户填写的表单内容就被发送到了服务器端,这时在服务器端就需要一个程序来对从客户端传来的数据进行处理:例如,将用户在表单上填写的数据进行整理和保存,或者是按用户提交的关键字进行查询等等。如果没有CGI,用户就只能在网页上浏览信息,所有的信息都变成单向的了,Web就失去了交互性。
例如,当某个网站要搞一次问卷调查,它可以在它的网页中设计一个表单,上面列出要用户来选择的项目和需要用户回答的问题,当用户在表单上作出选择或回答了问题并提交该表单后,这些数据就传回Web服务器,由表单的代码中指定的程序代码对这些数据进行处理。
从上面可以看到,HTML表单只完成了一半的工作,即客户数据的输入和提交。另一半工作,即数据的处理和存储,则由CGI程序来完成。
要指出的是,只能在客户端的浏览器中运行的JavaScript等是不可代替CGI程序的。JavaScript是在客户端运行,CGI是工作在服务器上。有些工作,如验证用户在表单上填写的数据是否正确等,JavaScript和CGI程序都可完成;有些工作,如在服务器端保存用户提交的数据或是在服务器的数据库中查询数据等,则只有CGI程序才能完成。
综上所述,CGI是用来沟通HTML表单和服务器端程序的接口(interface)。
1. 操作系统和Internet服务管理器
建议操作系统采用Windows 2000 Server,微软在此操作系统中提供了一个默认的Internet服务管理器(Internet Service Manager),当Windows
2000 server安装时,如果用户没有选择“不安装Internet服务管理器”,它就被自动安装。Internet服务管理器常常被称作“Internet信息服务器”(Internet Information Server),简称IIS。Windows 2000 Server中的IIS版为5.0。
如果采用Windows
NT Server 4.0操作系统则要另外安装IIS4.0。
2. 默认Web站点的设置
一般规模不大的校园网有一个Web站点就够了,所以我们可以不新建Web站点,只需对安装IIS时生成的默认Web站点进行适当的设置,使其满足我们的应用即可。
设置方法如下:
① 开始——程序——管理工具——Internet服务管理器。出现如图1的窗口。

图1
②右键单击“默认web站点”——属性。出现“默认Web站点属性”对话框。
③在“默认Web站点属性”对话框中选“主目录”,在此页框中:
在“内容应该来自于”单选钮组中选“此计算机上的目录”;
在“本地路径”文本框中输入存放主页的路径,如“e:\cdce_webs\”;
选中“读取”复选框;
在“执行许可”下拉列表中选“脚本和可执行程序”。
各参数设置情况如图2,设置好后单击“确定”。

图2
④再在“默认Web站点属性”对话框中选“文档”,在此页框中:
选中“启用默认文档”复选框;
单击“添加”按钮——在“添加默认文档”文本框中输入主页的文件名,如“cdce.html”——单击“确定”按钮回到“默认Web站点属性”对话框;
在文档文本框中选中刚才添加的文件名并用左边的箭头按钮将该文件名调整到最前面的位置。
设置好的情况如图3。

图3
至此,Web默认站点已设置好,只要作一个主页(文件名与上述第④步所设相同,如“cdce.html”)存放在上述第③步所设的本地路径(如e:\cdce_webs)中,校园网中其它计算机均可在Web浏览器中像访问Internet上的网站那样来访问该主页,即在浏览器的地址栏中输入服务器在局域网中的IP地址(如“http://192.168.0.1”)来访问该主页。如果已安装了代理服务软件(如MS
Proxy),并设置了代理服务器的名字(如cdce),则还可以用域名(如“http://cdce”)来访问主页。
3.
CGI编程语言的安装和设置
从理论上讲,只要符合CGI的规范,任何程序设计语言均可用来编写CGI程序。其中比较典型和应用广泛的是Perl。Perl是Practical
Extraction and Report Language的简称,由Larry Wall所发展。Perl原是运行于UNIX系统,现已广泛运用于OS/2,Windows
9x,Windows/NT/2000等操作系统。它之所以在CGI编程中得到广泛应用,主要是因为它对字符串的处理功能很强,还有就是它语法简单,容易使用。只要有一门高级语言编程的基础,如C、C++、Pascal,甚至Basic,都可极快地掌握使用这门语言。
Perl已有多个版本,作者在实践中采用的是Active
Perl,可从http://www.activestate.com/ASPN/Downloads/ActivePerl/免费下载,大小约8MB。
下载安装后,还需在IIS中按如下方法进行设置:
①启动Internet信息服务;
②在默认Web站点中增加一个虚拟目录:右键单击“默认Web站点”——新建——虚拟目录——下一步——在“别名”文本框中输入代表此虚拟目录的名称,如cgi_perl,下一步——在“目录”文本框中输入(浏览查找)存放Perl源程序的目录,如“E:\cdce_webs\cgi-shl”,下一步——在“允许下列权限”系列里选中“读取”和“运行脚本”、“执行”三个复选框,下一步——完成。
完成后,在Internet信息服务窗口的“默认Web站点”下面将出现新建的虚拟目录,如图4。

图4
③右键单击刚建立的虚拟目录,如cgi_perl——选取“脚本资源访问”和“读取”复选框——单击“配置”按钮——单击“添加”按钮——在“可执行文件”文本框中输入(或浏览查找)Perl的可执行文件的文件名(包括盘符路径),如“E:\Perl\bin\Perl.exe
%s %s”,在“扩展名”文本框中输入Perl源程序文件的扩展名“.pl”,确定。
完成后,在“应用程序配置”窗口的“应用程序映射”框中将会出现刚设置的内容,如图5。

图5
至此,CGI编程环境已设置好,以后只要把Perl源程序以.pl为扩展名存放到设定的目录,如E:\cdce_webs\cgi-shl,即可正常运行了。
Perl是解释运行的,一般Perl程序的第一行需注明自己是一个Perl程序而不是shell程序,所以一般将下面一行语句:
#!
<路径>/perl
作为文件的第一行。
表单数据提交时,向服务器提出请求,该请求定义了程序按什么格式接受数据,这就是数据的传送方法。常用的两种方法是:GET和POST。
用CGI程序处理表单时,一般指定“GET”方法,并在表单的ACTION属性值中指定CGI程序的URL地址。“GET”方法是把表单数据进行编码后附加到URL地址之后,然后传递给CGI程序进行处理的。
URL 编码是浏览器用来将表单输入数据进行打包的一种格式。
浏览器从表单中获取用户输入的数据,按“名/值”的格式将它们进行编码,作为URL的一部分发送给服务器。
例如,如果我们要制作一个查询表单,然后利用雅虎的搜索引擎去进行搜索,其HTML代码如下:
<FORM
METHOE=”GET”
ACTION="http://cn.search.yahoo.com/search/cn">
<INPUT TYPE=”TEXT” NAME=”p”>
<INPUT TYPE=”SUBMIT” VALUE=”查询”>
</FORM>
用户填写完搜索关键字后,单击“查询”按钮,浏览器就会把数据送至ACTION域中指定的URL地址进行查询。此处使用的是“GET”方法,数据被附加在URL地址的后面,因此yahoo的搜索程序是从URL中获得所有表单数据的。浏览器首先是在URL地址后面附加一个问号,然后是名字(此例中为“p”),接着是等号,最后是表单数据。例如,若要查询“abacus”这个词,提交的URL将是如下形式:
http://cn.search.yahoo.com/search/cn?p=abacus
yahoo的搜索引擎程序把单词“abacus”从URL中分离出来,然后在它的数据库去查找与“abacus”有关的内容。
在校园网的应用中,表单中的URL往往就是校园网中Web服务器上的一个CGI程序及所在的目录。另外,如果表单提交的数据不只一项时,在URL附加的部分是用一个“&”符号将其隔开。还有,若提交的数据是汉字,则提交的数据是汉字内码且每个字节之间用“%”隔开。例如,有下面的表单,要求用户输入姓名和E-mail地址:
<form
action="/cgi_perl/baoming.pl" method="get">
你的姓名:<input
type="text" name="name"><br>
你的E-mail地址:<input
type="text" name="email">
<input type="submit"
value="提交">
</form>
当用户输入的姓名是“傅叔平”、E-mail地址是“fsp@263.net”时,提交的URL将是如下的形式:
http://cdce/cgi_perl/baoming.pl?name=%B8%B5%CA%E5%C6%BD&email=fsp@263.net
CGI程序将从中分离出用户的姓名和E-mail地址。其中的“%B8%B5%CA%E5%C6%BD”就是“傅叔平”三个汉字的代码(至于如何对汉字进行解码,本文将在后面讲述)。
综上所述,URL编码遵循下列规则:
每对“名/值”由“&”分开;
每对来自表单的“名”和“值”之间有“=”分开;
非ASCII字符,如汉字及“=”、“&”、“%”等特殊的字符,用百分符“%”和十六进制编码表示;
输入的空格以加号“+”表示。
HTML表单提交的数据传送到服务器后,CGI程序将从环境变量中来获取这些数据。常用的环境变量有如下一些:
SERVER_NAME:CGI脚本运行时的主机名和IP地址。
SERVER_SOFTWARE:你的服务器的类型如:
CERN/3.0 或 NCSA/1.3。
GATEWAY_INTERFACE:运行的CGI版本.
对于UNIX服务器, 这是CGI/1.1。
SERVER_PROTOCOL:服务器运行的HTTP协议.
这里当是HTTP/1.0。
SERVER_PORT:服务器运行的TCP口,通常Web服务器是80。
REQUEST_METHOD:POST
或 GET, 取决于你的表单是怎样递交的。
HTTP_ACCEPT :浏览器能直接接收的Content-types, 可以有HTTP Accept header定义。
HTTP_USER_AGENT:递交表单的浏览器的名称、版本
和其他平台性的附加信息。
PATH_INFO:附加的路径信息,
由浏览器通过GET方法发出。
PATH_TRANSLATED:在PATH_INFO中系统规定的路径信息。
SCRIPT_NAME:指向这个CGI脚本的路径,
是在URL中显示的(如, /cgi-bin/thescript)。
QUERY_STRING:脚本参数或者表单输入项(如果是用GET递交)。
QUERY_STRING 包含URL中问号后面的参数。
REMOTE_HOST:递交脚本的主机名,这个值不能被设置。
REMOTE_ADDR:递交脚本的主机IP地址。
REMOTE_USER:递交脚本的用户名。
如果服务器的authentication被激活,这个值可以设置。
REMOTE_IDENT:如果Web服务器是在ident
(一种确认用户连接你的协议)运行, 递交表单的系统也在运行ident, 这个变量就含有ident返回值。
CONTENT_TYPE:如果表单是用POST递交,
这个值将是 application/x-www-form-urlencoded. 在上载文件的表单中, content-type 是个
multipart/form-data。
CONTENT_LENGTH:对于用POST递交的表单,
标准输入口的字节数。
其中REQUEST_METHOD、QUERY_STRING、CONTENT_LENGTH是三个非常重要的变量,它们用来表示数据是如何送到CGI程序的。CGI程序要做的事情就是在这三个变量里取出数据,进行下一步的处理。
例如,在Perl语言中,可以用以下语句从QUERY_STRING环境变量中得到URL编码。
$string=$ENV{'QUERY_STRING'};
CGI程序必须从表单提交的URL解出各个变量的名和变量的值,才谈得上进一步对这些数据进行处理。现在给出一个解码的实例。用Perl写的解码程序代码如下:
$string=$ENV{'QUERY_STRING'};
@temp
= split(/&/,$string);
@temp1
= split(/=/,@temp[0]); @temp2 = split(/=/,@temp[1]);
$name
= @temp1[1]; $email=@temp2[1];
$cname
= ToChnString($name);
现在以前面的例子中提交的如下URL为例来说明该程序代码:
http://cdce/cgi_perl/baoming.pl?name=%B8%B5%CA%E5%C6%BD&email=fsp@263.net
我们的任务是将用户提交的姓名“傅叔平”和E-mail地址“fsp@263.net”从上述URL中分离出来。
上面程序中的第一行是通过环境变量QUERY_STRING将 “?”后的字符串分离出来并存到变量$string中。此时$string的值是”name=%B8%B5%CA%E5%C6%BD&email=fsp@263.net”
第二行是从$string中分离出“&”隔开的两个字符串。此时@temp的值是(”name=%B8%B5%CA%E5%C6%BD”,”email=fsp@263.net”);
第三行的两个语句从@temp中的两个字符串中分离出“=”隔开的字符串。此时@temp1的值是(”name”,”%B8%B5%CA%E5%C6%BD”);@temp2的值是(” email ”,”fsp@263.net”);
第四行的两个语句分别将temp1和temp2中的第二个字符串存到变量$name和$email中,此时$name的值为”%B8%B5%CA%E5%C6%BD”,$email的值为”fsp@263.net”。
现在,用户从HTML表单提交的姓名和E-mail地址都得到了。但是,显然还有一个问题需要解决,那就是汉字信息还是十六进制代码的形式。为此,作者专门编写了一个汉字解码函数ToChnString来解决这个问题。在上面程序的最后一行,只需调用该函数即可将$name中的”%B8%B5%CA%E5%C6%BD”转换成汉字“傅叔平”。
下面就是汉字解码函数ToChnString的代码:
sub
ToChnString
{
my($i)=0;
my($chnstring)="";
my($code0)=0; my($code1)=0;
my($string)=@_;
for($i=0;$i<length($string);)
{
if(substr($string,$i,1) eq '%')
{
$temp=(substr($string,$i+1,1).substr($string,$i+2,1));
$chnstring.=chr(hex($temp));
$i+=3;
}
else
{
$chnstring.=substr($string,$i,1);
$i++;
}
}
return($chnstring);
}
该函数的算法是:扫描源串,如果遇到百分符“%”就将其后的两个字节转换为汉字并连接到变量chnstring中,否则认为是非汉字字符,不变,照原样连接到变量chnstring中。最后返回chnstring的值。
该函数没有处理其它用百分符“%”和十六进制编码表示的“=”、“&”、“%”等特殊的字符。因为在表单中提交的汉字串中这几个特殊字符用得极少,此处就简化了。如果表单中要提交这些字符,读者可对ToChnString函数进行修改,只要判断百分符后面是否是这几个字符的ASCII码就可与汉字分开处理了。
在网络的交互性中,CGI程序还有一个基本的任务,就是要向客户端返回表单提交后的处理结果等信息。这些信息要显示到客户端屏幕上的浏览器窗口中。我们可以在CGI程序中通过标准输出手段按HTML的规范输出信息,即可达到在客户端的浏览器窗口中显示信息的目的。下面举一个简单的Perl程序例子,该例在客户端的浏览器窗口中显示“祝贺你,你已报名成功!”的信息:
#!
e:\perl\bin\
print"Content-type:text/html\n\n";
print”<html>”;
print”<head>”;
print"<title>报名结果</title>";
print”</head>”;
print”<body>”;
print"<br><br>祝贺你,你已报名成功!<br>";
print"</body>";
print”</html>”;
上面程序在服务器端执行,它相当于在客户端执行了如下HTML代码:
<html>
<head>
<title>报名结果</title>
</head>
<body>
<br><br>祝贺你,你已报名成功!<br>
</body>
</html>
注意上述Perl程序的前两行:
第一行:
#!
e:\perl\bin\
注明以下的行是一个Perl程序而不是shell程序。一般都要将这行作为Perl源程序的第一行,同时也告之了Perl的可执行文件所在的目录。
第二行:
print"Content-type:text/html\n\n";
CGI程序产生的输出由两部分组成:MIME头信息和实际的信息。两部分之间以一个空行分开。MIME头信息"Content-type:text/html\n\n"表示输出HTML源代码给Web服务器。请注意任何MIME头信息后必须有一个空行(所以这一个语句末用了两个换行符)。一旦发送这个MIME头信息给Web服务器后,Web浏览器将认为随后的文本输出是HTML源代码。
用此方法产生的HTML源代码中可以使用任何HTML结构,如超级链接、图像、表单等等。也就是说,我们可以在CGI程序中动态地产生HTML源代码输出,这样就很容易实现服务器端的CGI程序与客户端的用户的交互性。
最后,举一个在校园网中实际应用的例子。任务是针对学院将要对教职员工举办的《电子备课》和《校园网应用基础》两个培训班实现网上报名。
程序一是用HTML写的表单程序,它的任务是在客户端的浏览器窗口中显示一个报名表,让报名者填写并提交。如图6。注意它的form的action为“/cgi_perl/baoming.pl”,即为表单提交后调用的CGI程序。程序一的源代码应存放在默认Web站点存放主页的目录中(参见《CGI软件环境的建立》一节)。

图6
程序二是用Perl语言写的CGI程序,它的任务是检验用户从客户端提交的“培训类别”、“系处室”、“姓名”和“E-mail地址”四个数据,如果正确则将它们写入服务器上的“baomingbiao.txt”文件之中并返回报名成功的信息;否则返回报名失败的信息。它的源代码应存放在Perl的可执行文件目录中(参见《CGI软件环境的建立》一节),文件的扩展名应为“.pl”,本例中的文件名为“baoming.pl”
请注意,在程序一和程序二中都有对用户的输入进行检验的程序段。程序一中是用JavaScript写的函数“function CheckValid(form)”;程序二中是用如下的语句:
if(length($cname)==0
|| length($email)!=0 && index($email,"@")==-1)
print" 报名失败,请返回重新填报!!!<cr>";
其实,既然客户端程序已进行了检验,服务器端的CGI程序就不必再作检验了。此处只是为了示例,说明此检验工作既可在客房端进行,也可在服务器端进行。那么,在哪里进行更好呢?由于在客房端运行程序比在服务器端运行快得多,所以像这种既可在客户端又可在服务器端完成的工作就最好在客户端进行。
程序一与程序二的源代码附后。它们均在Windows 2000 Server+IIS(服务器端)、Windows 98+Internet Explorer 5环境中实际使用。
程序一(HTML程序源代码)
<html>
<head>
<meta
http-equiv=Content-Type content=text/html; charset=gb2312>
<title>培训报名</title>
<style
type=text/css>
<!--
body{font-size:11pt;}
.p{font-size:12pt;color:red;}
.p1{font-size:10pt;}
-->
</style>
<script
language="JavaScript">
<!--
function CheckValid(form)
{
var ok=true;
if(form.name.value=="")
{
ok=false;
alert("未填姓名!");
form.name.focus();
}
else
if(form.email.value!="" &&
form.email.value.indexOf("@")==-1)
{
ok=false;
alert("错误的 E-mail 地址!");
form.email.focus();
}
return(ok);
}
//
-->
</script>
</head>
<body
bgColor=#ffe4c4 text=#000000>
<center>
<form
action="/cgi_perl/baoming.pl" method="get"
enctype=text/plain onsubmit="CheckValid(this)">
<table class="p1"
border="1" cellpadding="1" width="463">
<caption
class=p>
<p style="line-height: 150%">
<b><font color="#0000FF"><span
style="letter-spacing: 8pt"><big>培训报名表</big></span></font></b><br>
<font size="2">
(有**的为必填项)</font>
</caption>
<tr>
<td align="right" width="229">** 培训类别:</td>
<td width="226">
<input type="radio" name="class" value="校园网应用基础"
checked>校园网应用基础
<input
type="radio" name="class" value="电子备课">电子备课
</td>
</tr>
<tr>
<td align="right" width="229">** 系处室:</td>
<td width="377"><select size="1"
name="department">
<option>院办</option>
<option>党办</option>
<option>招生就业处</option>
<!-- 其它行政单位略 -->
<option>计算机系</option>
<option>经管系</option>
<!-- 其它系略 -->
</select></td>
</tr>
<tr>
<td align="right" width="229">** 姓名:</td>
<td width="226"><input type="text"
name="name" size="15"></td>
</tr>
<tr>
<td align="right" width="229"> 你的E-mail地址:</td>
<td width="226"><input type="text"
name="email" size="32"></td>
</tr>
</table>
<br>
<input type="submit"
value="提交">
<input type="reset"
value="重填">
</form>
</center>
</body>
</html>
程序二(CGI程序源代码)
#!
e:\perl\bin\
sub
ToChnString
{
my($i)=0;
my($chnstring)="";
my($code0)=0; my($code1)=0;
my($string)=@_;
for($i=0;$i<length($string);)
{
if(substr($string,$i,1) eq '%')
{
$temp=(substr($string,$i+1,1).substr($string,$i+2,1));
$chnstring.=chr(hex($temp));
$i+=3;
}
else
{
$chnstring.=substr($string,$i,1);
$i++;
}
}
return($chnstring);
}
print"Content-type:text/html\n\n";
print”<html>”;
print”<head>”;
print"<title>报名结果</title>";
print”</head>”;
print”<body>”;
$string=$ENV{'QUERY_STRING'};
@temp
= split(/&/,$string);
@temp1=split(/=/,@temp[0]);
@temp2=split(/=/,@temp[1]);
@temp3=split(/=/,@temp[2]);
@temp4=split(/=/,@temp[3]);
$class=@temp1[1];
$dep=@temp2[1]; $name=@temp3[1]; $email=@temp4[1];
$cclass=ToChnString($class);
$cdep=ToChnString($dep);
$cname=ToChnString($name);
if(length($cname)==0
|| length($email)!=0 && index($email,"@")==-1)
print" 报名失败,请返回重新填报!!!<cr>";
else
{