|
CGI脚本入门学习
目 录 学习CGI脚本 (脚本)
CGI 意思为 Common Gateway Interface, 一种基于浏览器的输入、在Web服务器上运行的程序方法. CGI脚本 使你的浏览器与用户能交互, 为了在数据库中寻找一个名词, 提供你写入的评论, 或者从一个表单中选择几个条目并且能得到一个明确的回答. 如果你曾经遇到过在web上填表或进行搜索, 你就是用的CGI脚本. 你那时也许没有意识到, 因为大部分工作是在服务器上运行的, 你看到的只是结果. 作为一个网页设计者, 你创建客户端的 CGI脚本, 服务器端的程序用来处理用户输入, 结果返回给用户. 在这里你将学习关于CGI脚本的一切:
本章节假设在UNIX系统下的。 CGI脚本是什么?CGI脚本简单地讲是个运行在Web服务器上的程序, 有浏览器的输入触发. 这个脚本通常象服务器和系统中其他程序如数据库的桥梁。 CGI 脚本难道不是一个真正的脚本?按照你的服务器的支持,
他们可能是一个编译好的程序或者批命令文件或者其他可执行的东西.
为了简单起见, 我们统称他们为脚本scripts. CGI 脚本是任何运行在web服务器上的程序. CGI意思是Common Gateway Interface。 CGI脚本是用下列两种方法使用的: 作为一个表单的ACTION 或 作为一个页中的直接link。 CGI脚本是怎样工作的?CGI脚本有服务器调用, 基于浏览器的数据输入. 图1 显示在浏览器、服务器和脚本之间是怎样的一个流程. 这有个简短的示意解释:
一个简单的例子这里详细一步一步地解释所有有关发生的细节。 Display Date处是个指向CGI脚本的连接. 它的HTML是这样的: <A HREF="http://www.popchina.com/cgi-bin/getdate">Display the Date</A> 说明是个CGI脚本是因为这里面有个cgi-bin的路径. 在许多服务器cgi-bin是仅能够放置CGI脚本 的目录. 当你选择这个连接时, 你的浏览器将向www.popchina.com服务器提出请求. 服务器接收这个请求计算出URL处的脚本文件名然后执行这个脚本. 这个getdate脚本, 在UNIX系统中执行是这样的: #!/bin/sh echo Content-type: text/plain echo /bin/date 第一行是个特殊的命令, 告诉UNIX系统这是个shell脚本; 真实的情况是从这行开始的下一行, 这个脚本做两件事:它输出行Content-type: text/plain, 接着开始一个空行;第二, 它调用UNIX系统时间date程序, 这样输出日期和时间. 脚本执行后输出应该这样: Content-type: text/plain Tue Oct 25 16:15:57 EDT 1994 这个Content-type是什么东东?它是个特殊的编码, Web服务器用来告诉浏览器输出这个文本是什么类型的. 这与HTML中Content-type含义是一样的。 这是最基本的, 实际情况要复杂得多, 总之可以用来理解浏览器、服务器和脚本之间是怎样工作的。 我能用CGI脚本吗?在你使用CGI脚本之前, 有两件事你也许要解决:CGI脚本 是个高级的Web特性并且需要你和Web 服务器管理者一样好的知识。 肯定吗?就是做不到, 学学也可以?好吧!让我们继续. 你的服务器配置允许CGI脚本吗?为了能写和运行CGI脚本, 你需要一个Web服务器. 不象通常的HTML文件, 你不能在本地系统上写或试验你的CGI脚本; 你得通过Web服务器来做这个. 但是即使你有一个Web服务器, 这个服务器必须特别地为运行CGI脚本配置一下. 那意味着你所有的脚本必须放置在一个叫做cgi-bin的目录下. 在编写CGI脚本之前, 询问你的服务器管理者是否允许你安装和运行CGI脚本, 并且如果可以的话, 他们必须放置在哪儿?还有, 你必须有个真正的Web服务器, 如果是FTP或Gopher服务器, 那你就不能用CGI. 如果你在自己的服务器上运行, 你必须特别地创造一个叫cgi-bin的目录, 并配置你的服务器认可这个目录为一个脚本目录. 也必须记住下面有关CGI脚本特点:
你会编程吗?初学者注意! 一般地, 你必须具备一些基本编程概念与方法。你必须有类似系统工作的经验。如果你没有这些背景, 你必须去学习, 好了, 费话不多说. 你必须用什么编程语言?你可以用你熟悉的任何语言编写CGI脚本, 只要你的脚本遵守下一节所陈列的规则即可, 只要那个语言能在你的Web服务器系统上运行. 在这本学习手册中, 仅用两种语言编写CGI脚本: UNIX shell和 Perl语言. 这个shell是适合在任何相近的UNIX系统上运行并且容易学习, 但是处理复杂的情况就困难了. Perl, 就要用这个语言了, 它是免费的, 这个语言是稳定和强大的, 类似C, 但它也是较难学习的. 你的服务器设置正确了吗?为了运行任何一个CGI脚本, 不管简单或复杂的, 你的服务器必须设置成能够运行他们, 必须放置在一个特定的目录, 必须有一个依赖你服务器设定的文件扩展名. 如果你是租用服务器, 就要是否允许运行CGI脚本. 如果你拥有自己的服务器, 检查你的服务器说明书是怎样处理CGI脚本的. 如果你用的不是UNIX?只好再找别的学习手册了。 解剖一个CGI脚本如果你编写它很久, 克服很多警告和配置, 恭喜你, 你已经会些CGI脚本, 并且可以在你的网页上使用了. 在这一章, 将学习脚本是怎样执行, 你的服务器又是怎样与他们对话产生回应的。 输出头部虽然你的CGI脚本可以让你做任何事情, 但是脚本的输出还是必须有一个规定形式.
这个 "脚本输出" 意思是指你的脚本发回服务器的数据. 在UNIX系统中,
输出是发向标准输出, 服务器从那儿检测它. 在其他系统和服务器,
你的脚本输出也许不一样了. 这个头部是实际不是文本的一部分, 是服务器与浏览器之间的信息协议, 你实际看不到。 有三个类型的头部: Content-type, Location, 和Status. Content-type 最普遍的。 有关content-type解释可以见有关HTML的说明, 一个你可以发出的特定编码象这样: Content-type: text/html 在这个例子中, 输出数据的类型是text/html; 换句话说, 他是个HTML文件.
注意content-type 后面必须跟一个空行. 如果你没有空行, 服务器将无法搞清这个头部在哪里结束。 输出数据你输出的数据应该符合你所规定的content-type; 如果content-type是text/html, 输出安置应该是在HTML. 如果content-type是image/gif, 输出应该是在一个二进制的GIF文件. 练习1: 小试试.T这是个简单的输出日期的简单脚本, 这个CGI脚本还检查看看我是否已经登陆到我的Web服务器, 并且报告发现了什么. <A HREF="http://www.popchina.com/cgi-bin/pinglaura">Is Laura Logged in?</A> 这是没有输入的脚本, 它只运行并且返回数据. 根据前面的阐述, 这个脚本内容是这样:: echo Content-type: text/html echo "<HTML><HEAD>" echo "<TITLE>Is Laura There?</TITLE>" echo "</HEAD><BODY>" ison='who | grep lemay' 试验结果及返回相应提示的脚本是这样: if [ ! -z "$ison" ]; then echo "<P>Laura is logged in."</P> else echo "<P>Laura isn't logged in."</P> fi 最后关闭HTML: echo "</BODY></HTML>" 现在你通过从命令行运行他, 测试一下, 你将得到一个结果说我未登陆你的系统, 当然不可能的, 他的输出是这样的: Content-type: text/html <HTML><HEAD> <TITLE>Are You There?</TITLE> </HEAD><BODY> <P>Laura is not logged in. </BODY></HTML> 这是输出的一个HTML文本, 这样你的浏览器能正常显示他, 因为他是个HTML文件。 现在将他copy到你的服务器的cgi-bin目录下, 去执行, 如果你不能达到CGI-bin目录, 你必须询问你的服务器管理者, 你不能理所当然地自己建立个CGI-bin目录, 那没用的。
这个例子完整的脚本如下: #!/bin/sh echo "Content-type: text/html" echo echo "<HTML><HEAD>" echo "<TITLE>Is Laura There?</TITLE>" echo "</HEAD><BODY>" ison='who | grep lemay' if [ ! -z "$ison" ]; then echo "<P>Laura is logged in" else echo "<P>Laura isn't logged in" fi echo "</BODY></HTML>" 带有参数的脚本为了传递一个参数给脚本, 可以在URL中使用 (?) 插入脚本名词和参数之间, 用加号(+) 表示每个单一的参数, 如: <A HREF="/cgi-bin/myscript?arg1+arg2+arg3">run my script</A> 当服务器接收到这个请求, 它传递 arg1, arg2, 和 arg3 参数给脚本. 你然后能在脚本中使用这些参数. 这个方法有时叫查询, 因为早期它用在搜索功能中. 练习2: 检查是否有人登陆.既然你知道怎样使用参数, 让我们继续上面的例子pinglaura, 通过修改这个例子我们得到下面这个脚本pinggeneric. 我们取个不同题目: #!/bin/sh echo "Content-type: text/html" echo echo "<HTML><HEAD>" echo "<TITLE>Are You There?</TITLE>" echo "</HEAD><BODY>" 在上面的例子中, 下一步应该是测试我是否登陆, 在这里我们用参数${1}代替我的名字lemay, ${1}作为第一个参数, ${2}作为第二个, ${3}作为第三个. ison='who | grep "${1}"' 剩下的所有修改如下: if [ ! -z "$ison" ]; then echo "<P>$1 is logged in" else echo "<P>$1 isn't logged in" fi echo "</BODY></HTML>" 好了, 让我们修改HTML页中的连接吧!原来是这样: <A HREF="http://www.lne.com/cgi-bin/pinglaura">Is Laura Logged in?</A> 修改为通用查询功能后是这样, 比如查询名字叫john的人是否登陆: <A HREF="http://www.lne.com/cgi-bin/pinggeneric?john">Is John Logged in?</A> 在你的服务器上试试, 看是否有结果。 传递其他信息给脚本有第二种方法传递信息给CGI脚本. 它叫作路径信息path information
用作那些在脚本调用是不用变更的参数,
象一个临时文件名或调用脚本自己的文件名.
正如你看到的, 在上面的例子问号后面的参数是因用户表单的输入而改变的.
路径信息Path info用作其他信息传递给脚本, 实际上, 你可以用它作任何事情.
路径信息Path information是一种不象通常参数脚本那样频繁传递信息的方法. 路径Path information通常是指Web服务器上的那些比如配置文件、临时文件或者被脚本因问题调用的文件等等此类文件. 看下面一个路径信息path information例子, : http://myhost/cgi-bin/myscript/remaining_path_info?arg1+arg2 当脚本运行时, 在路径中的信息将被放置于环境参数PATH_INFO. 你能在你的脚本内容中使用这些信息. 比如说, 让我们假设你在多页上已有多个连接到同一个脚本. 你能用这个路径信息显示那个有连接的HTML文件名. 这样, 在你完成处理你的脚本之后, 当你发回一个HTML文件时, 你能在这个文件里包含一个连接, 发回用户一开始那个页。 你会在下一章节学到更多路径信息:有用的表单和脚本. 待后来登出 创建一个特殊的脚本输出现在你已经学习了诸如输出数据 一般地HTML数据 发给浏览器解释显示的数据. 但是如果你不想把脚本结果作为一个数据流形式发回浏览器, 而是想把一个存在的页发回, 怎么办? 如果你只是要脚本做一些事而不让任何结果回答给浏览器, 怎么办? 不用怕, 这里开始解释这些情况. 用调用另一个文本作为响应CGI输出不是非得一个数据流, 有时可以告诉浏览器是存在服务器上的一个页, 为了发出这个信息, 看下面的例子: Location: ../docs/final.html 这个Location行用作通常的输出位置, 也就是说, 如果你用了Location, 你就不必再用象Content-type这样的数据输出(实际上, 你也不能). 正如Content-type, 你也必须在这一行后面跟一个空行. 指向这个文件的路径可以是一个URL或相对路径. 所有相对路径是指相对于脚本所在的位置. 例子中的final.html文本是在当前上一个目录下docs的目录下: echo Location: ../docs/final.html echo 你不能Content-type 和 Location两个输出同时使用. 比如,
如果你想输出一个标准页, 但是想在这个页尾加上客户的内容,
你就得用Content-type自行组建这两个部分.
注意:你可以用脚本命令打开一个当地文件作为数据直接将之输出. No Response有时对于一个CGI脚本也许一点没有输出. 有时你只是要从用户那儿收集点信息. 你就不用再调用一个新文本, 也不用输出结果或打开一个存在的文件. 在浏览器上的屏幕还是那个样子. 很幸运, 这一切很容易. 你只要输出下面这个命令即可(后面跟一个空行): echo Status: 204 No Response echo 这个Status头部提供状态码给服务器(并且也给浏览器). 特别的204将传递给浏览器, 如果能识别它, 它将什么也不做. 尽管无响应是一个官方HTTP规定的一部分, 但也并不是适合所有的浏览器, 也许会产生奇怪的结果, 那你要多试验试试看啦.
处理表单的脚本今天, 大多数CGI脚本是用来处理表单输入的. 这个过程大致象上面说阐述的一样, 但还是有些不同比如CGI脚本只要被调用;数据怎样从服务器被发向浏览器. , 记住, 大多数表单有两个部分: HTML的表单格式;处理表单数据的CGI脚本. 这个CGI脚本使用标签<FORM>属性调用的. 表单形式和表单脚本正如上面所说, 由于表单有两个部分. 如下: 这个ACTION属性包含着处理表单的脚本: <FORM ACTION="http://www.popchina.com/cgi-bin/processorscript"> 在这个表单中, 每个输入区都有一个NAME的属性, 用来称呼表单元素. 当这个表单数据被递交给你在ACTION中定义的CGI脚本, 这样这些name和输入内容被作为一个数字或字符传递给脚本. GET 和 POST表单从浏览器发给服务器有两种方法. GET 和 POST. 我们上面谈论的方法, 实际是GET, 它将数据打包放置在环境变量QUERY_STRING中作为URL整体的一部分传递给服务器。 POST做很多类似GET同样的事情, 不同的地方就是它是分离地传递数据给脚本. 你的脚本通过标准输入获取这些数据. (有些Web服务器是存储在临时文件中.) 这个QUERY_STRING环境变量将不再设置. 那你用那个方法呢? POST是个安全的方法, 尤其如果你的表单中有很多数据的话. 当你用GET, 这个服务器就分配变量QUERY_STRING给所有的表单数据, 但是这个变量可存储量是有限的. 换句话说, 如果你有很多数据但是你又用GET, 你会丢失很多数据. 如果你用POST, 你可以尽可能多地使用数据, 因为这些数据从来也不分配到一个变量里. URL 编码URL 编码是一种浏览器用来打包表单输入的格式. 浏览器从表单中获取所有的name和其中的值 , 将他们作为name/value参数编码, 移去那些不能传送的字符, 将数据排行等等, 这些还取决于你用GET还是POST?作为URL的一部分或者分离地发给服务器. 不管哪种情况, 在服务器端的表单输入格式样子象这样: theName=Ichabod+Crane&gender=male&status=missing&headless=yes URL编码遵循下列规则:
因为表单输入是用这个URL编码传递给你的脚本的, 在你用这些参数之前必须解码, 因为解码是个很普遍的工作, 可以有很多工具做这个工作 . 你没有必要自己写这个解码程序. 这里介绍一个叫uncgi的解码程序, 你可以从http://www.hyperion.com/~koreth/uncgi.html. 得到原码, 安装在你自己的cgi-bin目录下. 练习3: 告诉我你的名字.让我们以这个例子来说明. 这个输入被发给脚本, 然后发回显示一个hello的信息. 如果你在姓名输入处不输入任何东东, 会怎样?. 修改表单的HTML现在我们举一个真实的例子: <FORM METHOD=POST ACTION="../cgi-bin/form-name"> </FORM> 如果你在用uncgi从input中解码, 情况有点不同. 为了是uncgi正常工作, 你首先必须调用uncgi , 如果uncgi是个目录, 加上实际的脚本名, 象这样: <FORM METHOD=POST ACTION="../cgi-bin/uncgi/form-name"> </FORM> 这样, 你不必修改表单中原始的HTML; 原始的HTML可以工作得很好. 脚本处理表单输入的是个CGI脚本, 让我们来仔细地看看。 在脚本中第一步是解码, 在这个例子中, 我们已经使用uncgi解码输入数据, 实际这个表单已经为你做好解码. 通过建立一个uncgi的目录, 一旦表单递交给服务器, 服务器会自动进行解码, 这样, 所有的name/value已经准备就绪等待你的使用. 现在, 一个例子开始部分假设是下面这样: echo Content-type: text/html echo echo "<HTML><HEAD>" echo "<TITLE>Hello</TITLE>" echo "</HEAD><BODY>" echo "<P>" 接下来, 有两种情况要处理:一件是处理用户不输入名字的情况, 一个是如果输入了向他们说hello. 这个Name元素的值, 是包含在WWW_theName环境变量中. 用一个简单的测试命令(-z), 你能查看环境变量是否是空的还是包括相应的输出值: if [ ! -z "$WWW_theName" ]; then echo "Hello, " echo $WWW_theName else echo "You don't have a name?" fi 最后增加一个连接"go back" . 用来返回: echo "</P><P><A HREF="../lemay/name1.html">Go Back</A></P>" echo "</BODY></HTML>"
问题这里是使用CGI脚本比较普遍的问题:
CGI变量表2 总结那些环境变量.
表单输入的解码程序目前有两个程序: 通用目的的uncgi, 和cgi-lib.pl, 这是个Perl库, 用于perl编写的CGI脚本. 当然也有表单上载时可以解码的程序, 很少。 uncgi说明原码可以从 http://www.hyperion.com/~koreth/uncgi.html获得。 cgi-lib.pl这是由Steve Brenner编写的, 帮助你管理输入. 他能从GET和POST获取输入并且放置在一个Perl列表或阵列中. 更新的版本也能处理来自表单的文件上传. 从这儿可以得到信息与原码 http://www.bio.cam.ac.uk/cgi-lib. 如果你决定用Perl语言处理你的表单输入, cgi-lib是个很好的库. 为了使用cgi-lib.pl, 你通常要这样写: require 'cgi-lib.pl'; cgi-lib中尽管有很多子程序, 最重要的是ReadParse子程. ReadParse 读取输入方便地将name/value储存在一个Perl阵列中. 在你的Perl脚本中通常是这样调用的: &ReadParse(*in); 此例中, 阵列名是in, 可以随便取名的. 在表单输入解码后, 你能读取和处理这个name/value, 方法是象下面这样: print $in{'theName'}; 这个将显示名字name是theName的值value. 如果你有多个用同样名字的name对, cgi-lib.pl用(\0)分隔多个名字. 这样可以正常处理你的脚本. 解码上传的文件输入基于表单的文件上传需要不同的表单输入, 有一些程序可以对其进行解码。 cgi-lib.pl 后来版本可以很好支持, 在http://www.bio.cam.ac.uk/cgi-lib/ 了解更多的情况. 另一个处理用Perl编写的CGI地址是 http://valine.ncsa.uiuc.edu/cgi_docs.html . 自己做找专门书籍学习吧: ftp://ds.internic.net/rfc/rfc1867.txt. 非解剖的脚本头部按照本书阐述, 大多数情况可以正常操作, 在一些情况下不是这样的, 你可以翻阅说明书了解。 <ISINDEX> 脚本为了在CGI中完成讨论组, 我们看看叫<ISINDEX>的搜索. 这是早期在浏览器中用来向服务器发出搜索关键字的办法, 参看以前的资料。 总结CGI脚本, 有时叫服务器端脚本或网关脚本。 在internet上有很多免费资源, 你可以搜索下载读懂他们, 当然都是英文的, 如果你下决心翻译他们(可能更加强理解), 翻译稿可以e-mail我,
这样一举两得啊, 本入门说明可以自由拷贝, 但请不要删除板桥斋字样,
到底我花了一些时间在上面, 你我都是各取所需嘛:-)。 -【板桥里人】于98年4月在板桥斋翻译 |