CGI应用程序开发基础.pdf
《CGI应用程序开发基础.pdf》由会员分享,可在线阅读,更多相关《CGI应用程序开发基础.pdf(12页珍藏版)》请在淘文阁 - 分享文档赚钱的网站上搜索。
1、CGI 应用程序开发基础1CGI 脚本结构CGI 应用程序开发基础1CGI 脚本结构 当脚本被服务器引发时,服务器常常以两种途径之一向脚本传递信息:GET或POST。这两种方法被称为请求方法。所使用的请求方法是通过环境变量传给脚本,该环境变量叫作REQUEST_METHOD(还定义了另外两种请求方法一HEAD和PUT,但它们不是特别应用于CGI,并且不鼓励使用它们)。1)GET是对数据的一个请求同样的方法被用于获得静态文档。GET方法以附加在URL后面的参数发送请求信息。这些参数将放在环境变量QUERY_STRING中传给CGI程序。例如,有一个叫作Myprog.exe的脚本,从如下的链接启动
2、它:REQUEST_METHOD是GET,QUERY_STRING包含lname=b1ow&fname=joe。在“URL一编码”中将讨论QUERY_STRING的格式。问号从QUERY_STRING的起始处分隔开脚本名字。在一些服务器上,问号是强制性的,即使后面没有跟着QUERY_STRING。另一些服务器则允许用一个正斜杠代替问号或与之附加在一起。如果使用斜杠,服务器则用PATH_INFO而不是QUERY_STRING变量将信息传给脚本。2)当浏览器将数据从一个填写表单传给服务器时,发生POST操作。对于POST,QUERY一STRING可能为空或不空,这有赖于服务器。如果有信息,则其如G
3、ET的情况一样被格式化和传递。来自POST查询的数据使用STDIN从服务器传到脚本。由于STDIN是一个源,脚本需要知道有多少有效数据。于是服务器还提供了另一个变量,CONTENT_LENGTH,以指出到来数据的字节数。而POST的数据格式为:variable1=value1&variable2=value2&etc 你的程序必须检查REQUEST_METHOD环境变量以知道是否要读取STDIN。CONTENT_LENGTH变量一般只在REOUEST_METHOD为POST时有用。CGI应用的基本结构既简单又直接明了:初始化、处理、输出和终止。由于讨论的是概念、数据源、编程规则,所以在例子中将
4、使用伪码而不是使用某种特定语言。理想情况下,一个脚本具有如下形式(do-initialize,do-process和do-output代表恰当的子例程):程序开始 调用 do-initialize 调用 do-proces 调用 do一output 程序结束。实际情况并非这么简单。11初始化 脚本启动后必须做的第一件事是确定其输入、环境和状态。基本操作系统环境信息能以通常方式得到:在Windows NT或windows95中从系统注册区得到,在Unix系统中从标准环境变量得到,在别的Windows版本中从INI文件得到,等等。状态信息来自于输入,而不是操作环境或静态变量。记住:每当CGI脚本被
5、引发时,它都好象此前从未1被引发过。脚本不在调用之间持续运行,所有的东西都必须从头初始化,如下:1确定脚本是如何被引发的 典型情况下,这涉及读取REQUEST_METHOOD环境变量并分析其中的单词GET或POST。注意注意 尽管当前定义应用于coi的操作只有GET和posT,你或许会时不时地遇到PUT或HEAD,假王口你的服务器支持它并且用户的剜览器或一个机器人使用它就可能发生这种情况。PUl7k作为PosT的另选提供,但从未得多(认可的RFC资格,一般不被使用。HEAD被一些剜览器fotL器人(自动剜览器账用,仅用于提取HTML文在的头部,不适用于C6路程。此外还有一些古怪的请求方法。你的
6、代码应该检查是否为GET和PosT,拒绝任何其他方法,不要假设请求方法如果不是GET便是PosT,或者相反。2提取输入数据 如果方法是GET,必须获得、分析、解码QUERY_STRING环境变量。如果方法是POST,必须检查QUERY_STRING并还要分析STDIN。如果CONTENT_TYPE环境变量是设为application/x-www-form-urlencoded,来自STDIN的源也需要解码。12处理 脚本通过读取和分析其输入从而对环境初始化之后,便准备进入工作。在此阶段发生的事情则远没有初始化阶段那样确定。在初始化时,参数是知道的(或是可以被发现),所要做的任务对于各个脚本都多
7、多少少地相同。然而,处理阶段是脚本的核心,在此时要做的事情几乎完全依赖于脚本的目标。1处理输入数据 此时做什么取决于脚本。例如,你可以忽略全部输入而仅仅输出数据,可能以有条理格式化的HTML将输入在吐出去,或许会在一个数据库中猎取信息在将其显示出来,或者是从前没有想到的任何事情。处理数据一般意味着,以某种方式对其进行转换。在传统的数据处理术语中,这叫做转换步骤,因为,在面向批作业的处理中,程序读取一个记录并对其施加一些规则(转换它),然后将其写回。CGI程序很少被看作传统的数据处理,但思想是一样的。程序的处理数据阶段不同的CGI程序,在数据处理阶段,你拿到输入,并从其中做出一些新的东西来。2输
8、出结果 在一个简单的CGI脚本中,输出常常只是一个头部和一些HTML。更复杂些的脚本可能;输出图形、图形与文本的混和,或者为了用一些附加信息再次调用脚本而必要的全部信息。一个常用并且更精巧的技术是使用GET调用脚本一次,这可以用一个标准的标记做到。脚本可以感知它是用GET调用的,并动态地创建HTML表单一一包括隐藏变量和再次用POST调用脚本所需的代码。兼容性问题兼容性问题 在UNIX世界中,字符流是一种特殊的文件。默认地,STDIN和STDOUT是字符流。操作系统很有帮助地为你分析流,确保所通过的全是正确的7-bitASCII码,或者是认可的控制码。7-bit?是的。对于HTML,这没有问题
9、。然而,如果你的脚本发送图形数据,使用面向字符的流则意味着立即失败。解决方法是将流切换到二进制模式。在C语言中,可以使用setmode函数:setmode(fileno(stdout),O_BINARY)。通过setmode(fi1eno(stdout),O_TEXT)在流当中进行切换。一个典型的图形脚本以字符模式输出头部,而后切换到二进制模式用于图形数据。2 在Windows NT世界中,为了兼容性,流有着同样方式的行为。输出中的一个简单n,当写到STDOUT时,被变换为rn。一般的Windows NT调用,如write Fi1e(),不发生上述变换,如果同时想要一个回车和一个换行,则必须显
10、式地指出rn。字符模式和二进制模式的另一种说法是cooked和raw,知道这两个名词的人或许会使用它们,而不是更常见的说法。不管使用什么词,在什么平台上,关于流存在着另一问题:默认情况下,它们是有缓冲区的,意思是操作系统挂起数据,直至看见一个行结束符、缓冲区满或者流被关闭。这意味着,你如果将有缓冲区的prinif()语句同无缓冲区的fwriie()或fprintf()语句混合在一起,事情可能就变得混乱了,尽管它们都会是写到STDOUT。Printf()有缓冲区地将数据写到流,面向文件的例程则无缓冲区地输出数据。结果是乱序的一团糟。你可能将此归咎于后向兼容性。除了许多老程序之外,流实在没理由将默
11、认定为有缓冲区和cooked。这应当是在需要时可以打开的选项,而不是在不要时关闭。幸运的是,你能够用setvbuf(stdout,NULL,_IONBF,0)解决这一困难,这个函数关闭UTDOUT流的全部缓冲区。另一个解决是避免混和不同类型的输出语句,即使这样,也不能使cooked输出变成raw。所以最好是关闭所有缓冲区。许多服务器和浏览器不喜欢接收单调乏味的输入。注意注意 那些常把UNIX挂在嘴边的人可能会对名词CRLF(回车与换行)皱眉,而那些在其他平台上编程的人也许不认识n或rn。CRLF等于rn。C编程者用r表示一个回车(CR)符号,用n表示一个换行(LF)符。(对于Basic编程,L
12、F是Chr$(10,CR是Chr$(13)。)13终止 终止就是清理和退出。你如果对任何文件加了锁,则必须在程序结束前释放它们。你如果分配了内存、信号量或其他对象,也必须进行释放。不正确完成这些会导致脚本“昙花只能一现”。即脚本在第一次调用时能工作,而在以后的调用中就会崩溃。更有甚者,脚本由于没有正确释放资源和锁,将会妨碍甚至破坏其他脚本或服务器本身。在一些平台上一Windows NT最显著,UNIX次之文件句柄和内存对象在进程终止时会被关闭和收回。即使这样,依赖操作系统为你清理垃圾也非明智之举。例如,在Windows NT上,如果一个程序对一个文件全部或部分加锁,而后不释放锁便终止,则文件系
13、统的行为将是不确定的。必须确保你的出错一退出例程如果有(也应该有)了解脚本的资源并能象主退出例程一样彻底地对它们进行清理。2计划脚本2计划脚本 现在读者已经看到了一个脚本的基本结构,下面将要学习如何从头计划一个脚本。按照如下基本步骤进行:1.用一些时间定义程序的任务。整体、周到地考虑一下,把它写下来并描绘程序逻辑。当你已经很好理解了输入、输出和必须做的转换处理之后,才可以往下继续。2.预订好食物和饮料,把自己关在屋里一晚上,第二天便可以带着完成的程序出来了。如果前面第1步做得好,那么实际编写程序是算不了什么的(编写代码时不要忘记为它做文档)。3.测试、测试、测试。试一试各种知道的浏览器和各种能
14、想到的输入。尤其检测一下诸如用户在一个10字节字段中输入32KB数据,或者在期望为简单文字的地方输入控制代码等等这些情况。34.将整个程序文档作为一个整体不仅仅针对其中的单个步骤以便让其他人维护或改编代码时能够理解你的意图。当然,本节的话题是上面的第一步,因此下面让我们更深入地看一看此过程:1.如果你的脚本处理表单变量,计划出每个变量的名字、预期长度、数据类型。2.当你从QUERY_STRING或STDIN拷贝变量时,检查类型和长度是否正确。UNIX破坏者的一个惯用技俩是蓄意让缓冲区溢出,鉴于一些脚本语言(显著的是sh和bash)为变量分配内存的方式,使得破坏者能够访问本应受到保护的内存,他们
15、能在你脚本的堆或栈空间放置可执行指令。3.使用有意义的变量名字。一个指向环境变量QUERY_STRING的指针应该叫作类似PQueryString的名字,而不是P2。这不仅在一开始有助于调试,也能简化维护和修改工作。不管代码有多漂亮,也免不了在一年后想不起来P1是指向CONTENT_TYPE而P2指向QUERY_STRING。4.区分系统级参数和用户级参数。前者影响程序如何操作而后者提供实例特定的信息,例如,在一个发送电子邮件的脚本中,不要让用户指定SMTP主机的IP号。这个信息甚至不应该出现在隐藏变量里的表单上。它是实例无关的,因而应当是一个系统级参数。在Windows NT中,将该信息存在
16、注册区里,在UNIX中,将它放入一个配置文件或系统环境变量。5.如果你的脚本退出外壳(shell out)而到系统去加载另一程序或脚本,不要传递未经检查的用户给出的变量尤其在UNIX系统中,那里system()调用可包含管道或重定向符使不经检查的变量可引起灾难。聪明的用户和恶意的窃入者会用这种方式拷贝敏感信息或破坏数据。你如果不能完全避免system()调用,则要小心地计划。确切定义什么能作为一个参数传递,并且知道哪些bit来自用户。在程序中包含一个分析可疑字符串并将其排斥掉的算法。6.如果你的脚本存取外部文件,则要对如何处理并发做出计划。你可能会加锁部分或全部数据文件,建立一个信号量,或者使
17、用一个文件作为一个信号量,决不要假想你的脚本是存取某一给定文件的唯一程序而毫不顾虑并发问题。你的脚本的5个拷贝可能会同时运行用以满足来自5个不同用户的请求。注意注意 编程者使用信号量来同步多个程序、同一程序的多个实例,甚至是单个程序内的多个例程。一些操作系统具有对信号量的内置支持,另一些则要求编程者建立信号量策略。以最简单的含义,信号量象一个开关,它的状态可以被检测:开关是打开的吗?如果是,则这样做;否则那样做,文件经常被用作信号量(文件存在否?存在则这样做,否则那样做)。一个更复杂的方法是向文件加锁以实现互斥存取(如果能得到锁,这样做,否则,等待一会儿并重试)。在CGI编程中,信号量经常被用
18、于同步同一CGI脚本的多个实例。举例来说,如果你的脚本必须更新一个文件,它不能假设文件随时可以得到。如果恰好该脚本的另一实例正在更新文件之中呢?第二个进程则必须等待,直至前一个完成,否则文件会被致命地破坏掉。解决的办法是使用一个信号量。要检测你的脚本以确保信号量被清掉。如果没有,它进入一个短循环,间隔地检测信号量。当信号量已被清掉,应设置信号量以避免其他程序介入,而后,便执行其临界区一一在这种情况下,写入文件一一然后再次清除信号量,使其他实例又能得到机会。信号量就是这样提供了一种管理并发安全性的方式。7.如果需要加锁文件,则应使用最小限定。当仅仅读取一个数据文件时,对写加锁,并在读完之后立即释
19、放锁。当更新一条记录时,只对记录(或一定范围的字节加锁。理想情况下,锁逻辑应该紧紧围绕实际I/O调用。不要在程序一开头便打开一个文件并锁住它直至终止。如果必要,可以立刻打开文件但不要加锁,直到真正要用它时,这样能让其他应用或者你脚本的其他实例能工作平滑和快速。8.为意外事件准备良好的退出。举例来说,如果你的程序要求互斥地存取一个特定资源,准备好等待一段合理时间而后优雅地退出。决不要编写一个永久等待的调用。当你的程序从一个致命错误消亡时,要确保它在临终前对错误进行报告。错误报告应该使用简单明白的语言。如果可能,还应将错误写进一个日志文件,使得系统管理员能够知道它。9.你如果在使用一种GUI语言来
20、编写CGI脚本,不要把捕获的错误表现为一个屏幕上的消息盒。这是一个服务器应用,错误将很少被人注意到并且清除,你的程序会挡在那里直至系统管理员偶尔路过。埋藏4所有的错误,在能够存活的地方工作,把其他的都看成天灾吧。10.在启动代码编辑器之前,为你的例程写伪码,至少到一般逻辑结构一级。这常常有助于建立存根例程,使你能在开发中在程序时使用实际调用。存根例程(stub routine)是一个权宜之计,它并不实际做任何处理,仅仅接收最终例程期待的输入,返回结果一致的代码。11.对于复杂的项目,一个数据流图将大有稗益。数据流应该有别于逻辑源。数据在程序中按照某条路径流动,为各个程序片段所拥有,不管它是如何
21、被子例程变换的。12.尽量封装私有数据和处理。你的例程应该有一个确定的输入和输出一个门进,一个门出,并且要知道通过大门的是什么。你的例程如何完成其任务,这不是调用例程的事。这叫作“黑匣子方法”。从外面不应该看到箱子里面发生了什么,也不应对它产生影响。例如,一个正确封装的使用平面文件表的查找例程可以被置换为一个与后端数据库打交道的例程,而不用对程序的其余部分做何改变。13.在进行中为程序做文档。自组织文档的代码是最好的方法,带有一般的注释和用于分隔代码的额外空行,如果使用了含义明确,很有说明性的变量和函数名,则事情已经做了一半。但好的文档不仅仅指出一般代码是做什么的,还要说明为什么这样做。例如:
22、“给REQUEST-METHOD赋值pRequestMethod”指出代码是做什么的,“确定是否由GET或POST引发”则说明为何编写该段代码,并且,更理想的是引出下一段代码和文档:“如果由GET启动,做这个”或“如果由POST启动,做这个。”14.象计划输入那样仔细地定义输出。你给用户的消息应该是标准化的。例如,不要象这样报告一个文件加锁问题:“Couldnt obtain lock,Please try again later”,而报告一个栈溢出错误为“ERR4332”,成功消息也应该具有一致性,不要一次返回:you are the first visitor to this site s
23、ince l196 而下次回返:you are visitor number 2 since 01-01-96 如果按逻辑分类数据流和分组函数,则每种类型的消息应由相应于那种类型的例程产生。如果你把带有错误消息和ear1y-out成功消息的代码加入到程序的逻辑流中,那么终端用户来说,你的程序看上去不太一致,而对任何维护你的代码的人来说它是一团糟。注意注意 early-out算法是一种用预先定义好的答案来检测异常和无意义情况并退出的算法,它不是以执行算法来决定答案的。例如,除法算法通常以两个操作检测一个除,并做一个移位而非除。3 标准CGI环境变量 3 标准CGI环境变量 这里对常遇到的标准环境
24、变量作一简要总结。各个服务器一致地实现了其中大部分,但也有变化、例外和附加的情况。一般地,你更可能找到一个新的、没有归档的变量而非一个省略的归档变量。那么,唯一用来确认的办法就是检查你的服务器文献。本节内容来自于NCSA规范,是你所能找到的最接近“标准”的规范。NCSA CGI规范的URL如下:http:/www.w3.org/hypertextWWW/CGI/每当服务器加载脚本的一个实例时下述环境变量被设置,并且是私有和特定于该实例的:AUTH_TYPE 如果服务器支持基本的认证并且如果脚本被保护,此变量提供认证类型,此信息是特定于协议和服务器的。AUTH_TYPE的一个例子是BASIC。C
25、ONTENT_LENGTH 如果请求通过POST方法包括数据,此变量被设置为提供通过STDIN的字节的合法数据的长度如,72。CONTENT_TYPE 如果请求包括数据,此变量指定数据类型为一个MIME头一一例如,applicationx-www-form-5urlencoded GATEWAY_INTERFACE 它提供被服务器支持的CGI接口的版本数,其格式为CGI版本数:如CGI1.1。HTTP_ACCEPT 提供由逗号分开并被客户服务器可接受的MIME类型的列表,如image/gif,image/x-xbitmap,image/jpeg,image/pjpeg和*/*。此列表实际上来自
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- CGI 应用程序 开发 基础
限制150内