发帖  主题  评论  推荐  标签 用户 查搜   用户 密码 自动 注册  
 
面向对象 设计模式 领域驱动设计 云架构 框架 开发教程 SOA 大数据 扩展性 并发编程 EDA 分布式 函数编程
  CGI跟我学

板桥里人 http://www.jdon.com 2002年1月1日

  CGI 意思为 Common Gateway Interface, 一种基于浏览器的输入、在Web服务器上运行的程序方法. CGI脚本 使你的浏览器与用户能交互,为了在数据库中寻找一个名词, 提供你写入的评论,或者从一个表单中选择几个条目并且能得到一个明确的回答. 如果你曾经遇到过在web上填表或进行搜索, 你就是用的CGI脚本. 你那时也许没有意识到,因为大部分工作是在服务器上运行的,你看到的只是结果.

  作为一个网页设计者, 你创建客户端的 CGI脚本, 服务器端的程序用来处理用户输入, 结果返回给用户.

  在这里你将学习关于CGI脚本的一切:

  • CGI脚本是什么?它是怎样工作的
  • 一个CGI脚本输出象什么?
  • 怎样用参数或无参数创建一个CGI脚本
  • 怎样创建一个返回规定响应的CGI脚本
  • 怎样创建一个输入表单的CGI脚本
  • 有关在使用CGI脚本中的问题
  • 你能在脚本中使用的CGI变量

目 录



CGI脚本是什么?

  CGI脚本简单地讲是个运行在Web服务器上的程序, 有浏览器的输入触发. 这个脚本通常象服务器和系统中其他程序如数据库的桥梁。

  CGI 脚本难道不是一个真正的脚本?按照你的服务器的支持, 他们可能是一个编译好的程序或者批命令文件或者其他可执行的东西. 为了简单起见,我们统称他们为脚本scripts.

  CGI 脚本是任何运行在web服务器上的程序. CGI意思是Common Gateway Interface。

  CGI脚本是用下列两种方法使用的: 作为一个表单的ACTION 或 作为一个页中的直接link。

 

CGI脚本是怎样工作的?

  CGI脚本由服务器调用, 基于浏览器的数据输入. 其工作原理如下:
  1. 一个URL指向一个CGI脚本. 一个CGI脚本的URL能如普通的URL一样出现,区别于.htm/.html静态UR,CGI的URL是动态URL。如http://xxxx.com/cgiurl
  2. 服务器CGI接收浏览器的请求, 按照那个URL指向对应的脚本文件(注意文件的位置和扩展名),执行CGI脚本.
  3. CGI脚本执行基于输入数据的操作,包括查询数据库、计算数值或调用系统中其他程序.
  4. CGI脚本产生某种Web服务器能理解的输出结果.
  5. 服务器接收来自脚本的输出并且把它传回浏览器,让用户了解处理结果。

一个简单的例子

  这里详细一步一步地解释所有有关发生的细节。
  假设有一个Html页面有一个指向CGI脚本的链接:
<A HREF="http://www.jdon.com/cgi-bin/getdate">Display the Date</A>
  这个链接指向的是一个CGI脚本,因为其中有cgi-bin的路径. 在许多服务器cgi-bin是仅能够放置CGI脚本 的目录。

  当你选择这个链接时, 你的浏览器将向www.jdon.com服务器提出请求. 服务器接收这个请求计算出URL处的脚本文件名然后执行getdate这个脚本.

  这个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脚本特点:

  • 每个脚本是个程序, 它运行在浏览器可以请求的系统上, 执行时使用CPU时间和内存. 如果有成打上千的这些脚本同时运行,会怎样?你的系统将不忍负载直至崩溃。

  • 如果你不仔细地编写你的CGI脚本, 你将有可能让别人通过你的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的说明, 发出text/html特定编码如下:

Content-type: text/html
  在这个例子中,输出数据的类型是text/html; 换句话说, 输出的是个HTML文件.

    表1. 通用格式和content-types.

Format
Content-Type
HTML text/html
Text text/plain
GIF image/gif
JPEG image/jpeg
PostScript application/postscript
MPEG video/mpeg

  注意content-type 后面必须跟一个空行. 如果你没有空行,服务器将无法搞清这个头部在哪里结束。

输出数据  

  你输出的数据应该符合你所规定的content-type; 如果content-type是text/html, 输出安置应该是在HTML. 如果content-type是image/gif, 输出应该是在一个二进制的GIF文件.

 

练习1: 小试试.
  这是个简单的输出日期的简单脚本,这个CGI脚本还检查看看是否已经登陆到Web服务器,并且报告发现了什么。

  调用代码这样:

<A HREF="http://www.jdon.com/cgi-bin/pinglaura">Is Laura Logged in?</A>
  这是没有输入的脚本,它只运行并且返回数据.

  pinglaura脚本内容是这样::


#!/bin/sh

echo Content-type: text/html

echo "<HTML><HEAD>"

echo "<TITLE>Is Laura There?</TITLE>" 

echo "</HEAD><BODY>"
  为了测试是否已经登陆系统,用who命令(登陆名假设为lemay), 储存结果在变量ison中. 如果登陆, 变量ison将有些登录内容,否则是空的.

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>"
  现在你通过从命令行运行这个脚本,测试一下,你将得到一个结果说XXX未登陆你的系统,输出是这样的:
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
  当脚本运行时,在路径中的信息"myscript"将被放置于环境参数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脚本是使用Html标签<FORM action=..>属性调用的.

 

表单形式和表单脚本

  正如上面所说,由于表单有两个部分. 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编码遵循下列规则:
  • 每对name/value由&符分开.
  • 每对来自表单的name/value由=符分开. 如果用户没有输入值给这个name,那么这个name还是出现,只是无值(象这样 "name=").
  • 任何特殊的字符(就是那些不是简单的七位ASCII,如汉字) 将以百分符%用十六进制编码. 当然也包括象 =, &, 和 % 这些特殊的字符.
  • 在输入区中的空格将以加号+显示.
  因为表单输入是用这个URL编码传递给你的脚本的,在你用这些参数之前必须解码,因为解码是个很普遍的工作,可以有很多工具做这个工作 . 你没有必要自己写这个解码程序.

  这里介绍一个叫uncgi的解码程序, 你可以从http://www.hyperion.com/~koreth/uncgi.html. 得到原码,安装在你自己的cgi-bin目录下.

 

CGI变量

  表2 总结了环境变量. 如下
环境变量         
意义
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 递交表单的浏览器的名称、版本 和其他平台性的附加信息。
HTTP_REFERER 递交表单的文本的 URL,不是所有的浏览器都发出这个信息,不要依赖它
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递交的表单, 标准输入口的字节数.

表单输入的解码程序

  目前有两个程序: 通用目的的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,你通常要这样写:
#!/usr/lib/perl

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上有很多免费资源,你可以搜索下载读懂他们,当然都是英文的,如果你下决心翻译他们(可能更加强理解). 这样一举两得啊.

  注意:上述程序可以用ultra edit来编辑,注意转换UNIX格式 ,必须采用UNIX格式存盘,再上载,用telnet登陆,在命令行键入perl sample.pl,看有无bug,再 在浏览器中调用。CGI程序包括放置CGI的目录一定要改属性为777, 要写入的HTML文件也要改属性为777.

 

服务器后端性能大比拼

Java NIO原理和使用

Netty原理和使用

Java实用系统开发指南

语言平台专题

后端性能专题

JavaEE教程

Node.js教程

--【板桥里人 】于98年4月在板桥斋翻译

解道移动版 | 关注解道 | 联系解道 | 关于解道 | 广告联系 | 网站地图 | 设为首页

沪ICP证12033263 如有意见请与我们联系 Powered by JdonFramework
返回顶部