为什么买的虚拟主机的 "public_html" 目录下总有那么一个空 "cgi-bin" 目录?再者为什么我将 cgi 文件不放刚才那个"cgi-bin" 目录下也同样也能运行?什么时候需要用到 AddType 指令

一、基础知识

1.1、什么是 CGI?

见前文《Linux 从源码编译安装 PHP 5.3.4》,简单说 CGI 是一种口,所有对 CGI 文件的访问请求,Web 服务器必须先作执行处理而不能直接把内容返回浏览器。

1.2、什么是 Apache PMA

Apache PMA,即多路处理模块,详细介绍见 ,apache2.2 官方文档

apache2.2 默认使用 prefork ,如果需要选择 worker ,那么必须在apache2.2 编译时使用“--with-mpm=” 选项静态编译进核心。可通过"httpd -l"查询当前的PMA模式。

prefork 采用进程的方式,Worker 采用进程与线程混合方式,海量的请求时,prefork 较快但耗资源,worker 相反。

1.3、mod_cgi 和 mod_cgid 区别

Apache PMA 为 prefork 时使用mod_cgi模块,PMA 为worker 时使用mod_cgid模块。详细介绍见文档 Apache Module mod_cgiApache Module mod_cgid。即在用户层面,这两个模块本质上是相同的。

二、执行 CGI 的方法

2.1、指定ScriptAlias目录为CGI文件目录

在 ScriptAlias 目录的文件均被认为是 CGI i脚本,另外要设置该目录属性,但无需添加 ExecCGI 选项。

指定目录别名及路径,注意是“ScriptAlias”而不是“Alias”

1
ScriptAlias /cgi-bin/ "/usr/local/apache/cgi-bin/"

设置该目录属性,绝对路径,千万不要漏了 "Allow from all" 切记!

1
2
3
4
5
6
<Directory "/usr/local/apache/cgi-bin">
    AllowOverride None
    Options None
    Order allow,deny
    Allow from all
</Directory>
2.2、指定特定文件后缀为CGI文件

在 ScriptAlias 目录的文件均被认为是 CGI i脚本,那么目录之外是否可以?可以!但要指定后缀、设置目录属性,且启用ExecCGI选项。

指定后缀有两种方法,一是定义MIME类型为application/x-httpd-cgi,二是使用 AddHandler 或 SetHandler 指令激活内置的 cgi-script 处理器

1
2
AddType application/x-httpd-cgi .cgi #方法一
AddHandler cgi-script .cgi #方法二

设置CGI的目录属性,必须在Options指令中启用ExecCGI选项

1
2
3
4
5
6
<Directory "/usr/local/apache/htdocs/cgi-bin">
Options ExecCGI
AllowOverride None
Order allow,deny
Allow from all
</Directory>

简介
相关模块 相关指令
mod_alias
mod_cgi
 AddHandler
Options
ScriptAlias
 

CGI(公共网关接口)定义了web服务器与外部内容生成程序之间交互的方法,通常是指CGI程序或者CGI脚本,它是在网站上实现动态页面的最简单和常用的方法。本文将对如何在Apache web服务器上建立CGI以及如何编写CGI程序进行介绍。

配置Apache以允许CGI
要让CGI程序能正常运作,必须配置Apache以允许CGI的执行,其方法有多种。
ScriptAlias
ScriptAlias指令使Apache允许执行一个特定目录中的CGI程序。当客户端请求此特定目录中的资源时,Apache假定其中所有的文件都是CGI程序并试图运行它。
ScriptAlias指令形如:
ScriptAlias /cgi-bin/ /usr/local/apache2/cgi-bin/
如果Apache被安装到默认位置,默认的配置文件httpd.conf中就会有上述配置。ScriptAlias与Alias指令非常相似, 都是定义了映射到一个特定目录的URL前缀,两者一般都用于指定位于DocumentRoot以外的目录,其不同之处是ScriptAlias又多了一层 含义,即URL前缀后面的任何文件都被视为CGI程序。所以,上述例子会指示Apache:任何以/cgi-bin/开头的资源都将映射到/usr /local/apache2/cgi-bin/目录中,且视之为CGI程序。
例如,如果有URL为 http://www.example.com/cgi-bin/test.pl的请求,Apache会试图执行/usr/local/apache2/cgi-bin/test.pl文件并返回其输出。当然,这个文件必须存在而且可执行,并以特定的方法产生输出,否则Apache返回一个出错消息。
ScriptAlias目录以外的CGI
由于安全原因,CGI程序通常被限制在ScriptAlias指定的目录中,这样,管理员就可 以严格控制谁可以使用CGI程序。但是,如果采取了恰当的安全措施,则没有理由不允许其他目录中的CGI程序运行。比如,你可能希望用户在UserDir 指定的宿主目录中存放页面,而他们有自己的CGI程序,但无权访问cgi-bin目录,这样,就产生了运行其他目录中CGI程序的需求。
允许CGI在任意目录执行需要两个步骤:第一步,必须用AddHandler或SetHandler指令激活cgi-script处理器。第二步,必须在Options指令中启用ExecCGI选项。
用Options显式地允许CGI的执行
可以在主配置文件中,使用Options指令显式地允许特定目录中CGI的执行:
<Directory /usr/local/apache2/htdocs/somedir>
Options +ExecCGI
</Directory>
上述指令使Apache允许CGI文件的执行。另外,还必须告诉服务器哪些文件是CGI文件。下面的AddHandler指令告诉服务器所有带有cgi或pl后缀的文件是CGI程序:
AddHandler cgi-script .cgi .pl
.htaccess文件
.htaccess指南示范了怎样在没有权限修改httpd.conf文件的情况下激活CGI程序。
用户目录
为了允许用户目录中所有以".cgi"结尾的文件作为CGI程序执行,你可以使用以下配置:
<Directory /home/*/public_html>
Options +ExecCGI
AddHandler cgi-script .cgi
</Directory>
如果你想在用户目录中指定一个cgi-bin子目录,其中所有的文件都被当作CGI程序,你可以这样配置:
<Directory /home/*/public_html/cgi-bin>
Options ExecCGI
SetHandler cgi-script
</Directory>

编写CGI程序
编写CGI程序和"常规"程序之间有两个主要的不同。
首先,在CGI程序的所有输出前面必须有一个HTTP的MIME类型的头,对客户端指明所接收内容的类型,大多数情况下,像这样:
Content-type: text/html
其次,输出要求是HTML形式的,或者是浏览器可以显示的其他某种形式。多数情况下,输出是HTML形式的,但偶然也会输出一个gif图片或者其他非HTML的内容。
除了这两点,编写CGI程序和编写其他程序大致相同。
第一个CGI程序
这个CGI程序的例子在浏览器中打印一行文字。把下列存为first.pl文件,并放在你的cgi-bin目录中。
#!/usr/bin/perl
print "Content-type: text/html\n\n";
print "Hello, World.";
即使不熟悉Perl语言,你也应该能看出它干了什么。第一行,告诉Apache这个文件可以用/usr/bin/perl(或者任何你正在使用 的shell)解释并执行。第二行,打印上述要求的内容类型说明,并带有两个换行,在头后面留出空行,以示HTTP头的结束。第三行,打印文 字"Hello, World."。程序到此结束。
打开你喜欢的浏览器并输入地址:
或者是你存放程序的其他位置,就可以在浏览器窗口中看到一行:Hello, World. 。虽然并不怎么激动人心,但是一旦这个程序能正常运行,那么就可能运行其他任何程序。

程序还是不能运行!
使用浏览器从网络访问CGI程序,可能会发生四种情况:
CGI程序的输出
太好了!这说明一切正常。如果输出正常但是浏览器处理出错,请确认你在CGI程序中使用了正确的 Content-Type 。
CGI程序的源代码或者一个"POST Method Not Allowed"消息
这说明Apache没有被正确配置以执行CGI程序,重新阅读配置Apache看看遗漏了什么。
一个以"Forbidden"开头的消息
这说明有权限问题。参考Apache错误日志和下面的文件权限。
一个"Internal Server Error"消息
查阅Apache错误日志,可以找到CGI程序产生的出错消息"Premature end of script headers"。对此,需要检查下列各项,以找出不能产生正确HTTP头的原因。
文件的权限
记住,服务器不是以你的用户身份运行的,在服务器启动后,拥有的是一个非特权用户的权限(通常是nobody或www)而需要更大的权限以允许文件的执行。通常,给予nobody足够的权限以执行文件的方法是,对文件赋予任何人皆可执行的权限:
chmod a+x first.pl
另外,如果需要对其他文件进行读取或写入,也必须对这些文件赋予正确的权限。
路径信息和环境变量
当你在命令行执行一个程序,某些信息会自动传给shell而无须你操心,比如PATH ,告诉shell你所引用的文件可以在哪儿找到。
但是,在CGI程序通过web服务器执行时,则没有此PATH ,所以,你在CGI程序中引用的任何程序(如sendmail)都必须指定其完整的路径,使shell能找到它们以执行你的CGI程序。
一种普通的用法是,在CGI程序的第一行中指明解释器(通常是perl),形如:
#!/usr/bin/perl
必须保证它的确指向解释器。
另外,如果CGI程序依赖于某些环境变量,你要确保所需要的变量已经正确的由Apache进行了传递。
程序错误
多数CGI程序失败的原因在于程序本身有问题,尤其是在已经消除上述两种错误而CGI挂起的情况下。在用浏览器测试以前,先在命令行中执行你的程序,能够发现大多数的问题。比如:
cd /usr/local/apache2/cgi-bin
./first.pl
(不要调用perl解释程序,因为shell和Apache会根据脚本第一行的路径信息找到解释器)
你最先看到的输出内容应当是一组HTTP头,包括Content-Type和结尾的空行。如果你看到了别的什么东西,那么当你在服务器上试运行 时,Apache会返回 Premature end of script headers 错误。参见上面的编写CGI程序以获得更多信息。
错误日志
错误日志是你的朋友。任何错误都会在错误日志中有所记载,所以你应该首先查看它。如果你的网站空间提供者不允许访问错误日志,那么你应该考虑换一个空间提供者。学会阅读错误日志,可以快速找出问题并快速解决。
Suexec
suexec允许CGI程序根据其所在虚拟主机或用户宿主目录的不同而以不同的用户权限运行。suexec有极其严格的权限校验,任何校验失败都会使CGI程序遭遇 Premature end of script headers 错误。
为了检查你是否使用了suexec ,运行 apachectl -V 并检查SUEXEC_BIN的位置。如果Apache在启动时发现suexec二进制文件正存在于此,那么suexec将会被激活。
除非你很精通suexec,否则请不要使用它。要禁用它,只要删除(或重命名)SUEXEC_BIN所指定位置的suexec二进制文件并重启 服务器就可以了。如果你又想启用它,请首先阅读suexec文档以详细了解其运行机制,然后运行 suexec -V 命令找到suexec日志文件,并使用该日志文件找到你违反了哪条判断规则。

幕后是怎样操作的?
当你的CGI编程逐渐深入,理解幕后的操作(尤其是浏览器和服务器之间是如何通讯的)就变得很有用了。因为虽然成功地写了一个程序打印"Hello, World",但并没有实际的用处。
环境变量
环境变量是使用计算机时到处都会用到的变量,比如路径(对实际文件的一个搜索路径以补全你的输入)、你的用户名以及你的终端类型等等。在命令行输入 env ,可以得到当天标准的环境变量列表。
在CGI处理过程中,服务器和浏览器都会设置环境变量,比如浏览器类型(Netscape、IE、Lynx)、服务器类型(Apache、IIS、WebSite)以及将要执行的CGI程序名称等等。
所有这些变量对CGI程序员都有效,但只是客户端-服务器通讯的一半内容。完整的变量列表参见 http://hoohoo.ncsa.uiuc.edu/cgi/env.html
这个简单的CGI程序列出了所有的环境变量,Apache发行版的cgi-bin目录中还有一个类似的程序。注意,有些变量是必须的,有些则是 可选的,所以你可能会看见一些官方列表中没有的变量。另外,Apache有多种方法可以在默认提供的变量之外增加你的专用环境变量。
#!/usr/bin/perl
print "Content-type: text/html\n\n";
foreach $key (keys %ENV) {
print "$key --> $ENV{$key}<br>";
}
STDIN 和 STDOUT
服务器和客户端之间的其他通讯都通过标准输入设备(STDIN)和标准输出设备(STDOUT)完成。通常,STDIN是指键盘或者一个程序所作用的一个文件,STDOUT指控制台或显示器。
当你POST一个网络表格到一个CGI程序时,表格中的数据被捆扎为一个特殊形式通过STDIN传送给CGI程序,这样,这个程序就可以处理这些数据,仿佛这些数据是来自键盘或者一个文件。
这种"特殊形式"很简单,一个字段名称及其值,中间用等号(=)连接,多个这样的字段对用与符号(&)连接。非常规字符,如空格、"&"号和"="号,被转换为其等值的十六进制以免出问题。整个字符串形如:
name=Rich%20Bowen&city=Lexington&state=KY&sidekick=Squirrel%20Monkey
有时,你会发现URL后面也会带有这样的字符串。这种形式会使服务器以这个字符串的内容设置环境变量QUERY_STRING ,称为GET请求。你的HTML表格在FORM标签中设置METHOD属性,以指定传送数据的动作使用GET或POST 。
你的程序必须把这个字符串分解以获得有用信息。所幸,有库和模块可以帮助你处理这些数据,还有为你的CGI程序达成其他目的的处理器。

CGI模块/库
编写CGI程序时,你应该考虑使用代码库或模块来完成大多数琐碎的工作,以减少错误并更快地开发。
如果用Perl语言编写CGI程序,可用的模块见CPAN ,最常用的模块是CGI.pm 。也可以考虑用CGI::Lite ,它实现了一个在多数程序中所有必须的最小功能集。
如果用C语言编写CGI程序,则有很多选择,其中之一是CGIC库,来自 http://www.boutell.com/cgic/

更多信息
网上有大量的CGI资源。可以在Usenet组comp.infosystems.www.authoring.cgi和别人讨论CGI相关问题。HTML Writers Guild 的邮件列表是一个优秀的问题解答资源。更多资源在 http://www.hwg.org/lists/hwg-servers/
另外,还可以阅读CGI规范,其中有CGI程序操作的所有细节,原始版本见NCSA ,另有一个更新草案见Common Gateway Interface RFC project
当你向一个邮件列表或者新闻组提交CGI相关问题时,你应该确保提供了足够的信息以更容易地发现并解决问题,诸如:发生了什么事、你希望得到什么结果、结果与你所期望的有什么出入、你运行的服务器、CGI程序是用什么语言编写的、如果可能就提供那个讨厌的代码。

注意,不要把CGI相关问题提交到Apache bug数据库,除非你坚信发现的是Apache源代码中的问题。