# CGI动态页面
## 简介
相关模块
* `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`指南](#calibre_link-222)示范了怎样在没有权限修改`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类型](#calibre_link-223 "see glossary")的头,对客户端指明所接收内容的类型,大多数情况下,像这样:
```
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."。程序到此结束。
打开你喜欢的浏览器并输入地址:
```
http://www.example.com/cgi-bin/first.pl
```
或者是你存放程序的其他位置,就可以在浏览器窗口中看到一行:`Hello, World.` 。虽然并不怎么激动人心,但是一旦这个程序能正常运行,那么就可能运行其他任何程序。
## 程序还是不能运行!
使用浏览器从网络访问CGI程序,可能会发生四种情况:
CGI程序的输出
太好了!这说明一切正常。如果输出正常但是浏览器处理出错,请确认你在CGI程序中使用了正确的 `Content-Type` 。
CGI程序的源代码或者一个"POST Method Not Allowed"消息
这说明Apache没有被正确配置以执行CGI程序,重新阅读[配置Apache](#calibre_link-224)看看遗漏了什么。
一个以"Forbidden"开头的消息
这说明有权限问题。参考[Apache错误日志](#calibre_link-225)和下面的[文件权限](#calibre_link-226)。
一个"Internal Server Error"消息
查阅[Apache错误日志](#calibre_link-225),可以找到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程序依赖于某些[环境变量](#calibre_link-227),你要确保所需要的变量已经正确的由Apache进行了传递。
### 程序错误
多数CGI程序失败的原因在于程序本身有问题,尤其是在已经消除上述两种错误而CGI挂起的情况下。在用浏览器测试以前,先在命令行中执行你的程序,能够发现大多数的问题。比如:
```
cd /usr/local/apache2/cgi-bin
./first.pl
```
(不要调用`perl`解释程序,因为shell和Apache会根据脚本第一行的[路径信息](#calibre_link-228)找到解释器)
你最先看到的输出内容应当是一组HTTP头,包括`Content-Type`和结尾的空行。如果你看到了别的什么东西,那么当你在服务器上试运行时,Apache会返回 `Premature end of script headers` 错误。参见上面的[编写CGI程序](#calibre_link-229)以获得更多信息。
### 错误日志
错误日志是你的朋友。任何错误都会在错误日志中有所记载,所以你应该首先查看它。如果你的网站空间提供者不允许访问错误日志,那么你应该考虑换一个空间提供者。学会阅读错误日志,可以快速找出问题并快速解决。
### Suexec
[suexec](#calibre_link-230)允许CGI程序根据其所在虚拟主机或用户宿主目录的不同而以不同的用户权限运行。suexec有极其严格的权限校验,任何校验失败都会使CGI程序遭遇 `Premature end of script headers` 错误。
为了检查你是否使用了suexec ,运行 `apachectl -V` 并检查`SUEXEC_BIN`的位置。如果Apache在启动时发现`suexec`二进制文件正存在于此,那么suexec将会被激活。
除非你很精通suexec,否则请不要使用它。要禁用它,只要删除(或重命名)`SUEXEC_BIN`所指定位置的`suexec`二进制文件并重启服务器就可以了。如果你又想启用它,请首先阅读[suexec文档](#calibre_link-230)以详细了解其运行机制,然后运行 `suexec -V` 命令找到suexec日志文件,并使用该日志文件找到你违反了哪条判断规则。
## 幕后是怎样操作的?
当你的CGI编程逐渐深入,理解幕后的操作(尤其是浏览器和服务器之间是如何通讯的)就变得很有用了。因为虽然成功地写了一个程序打印"Hello, World",但并没有实际的用处。
### 环境变量
环境变量是使用计算机时到处都会用到的变量,比如路径(对实际文件的一个搜索路径以补全你的输入)、你的用户名以及你的终端类型等等。在命令行输入 `env` ,可以得到当天标准的环境变量列表。
在CGI处理过程中,服务器和浏览器都会设置环境变量,比如浏览器类型(Netscape、IE、Lynx)、服务器类型(Apache、IIS、WebSite)以及将要执行的CGI程序名称等等。
所有这些变量对CGI程序员都有效,但只是客户端-服务器通讯的一半内容。完整的变量列表参见[http://hoohoo.ncsa.uiuc.edu/cgi/env.html](http://hoohoo.ncsa.uiuc.edu/cgi/env.html)
这个简单的CGI程序列出了所有的环境变量,Apache发行版的`cgi-bin`目录中还有一个类似的程序。注意,有些变量是必须的,有些则是可选的,所以你可能会看见一些官方列表中没有的变量。另外,Apache有多种方法可以在默认提供的变量之外[增加你的专用环境变量](#calibre_link-232)。
```
#!/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](http://www.cpan.org/) ,最常用的模块是`CGI.pm` 。也可以考虑用`CGI::Lite` ,它实现了一个在多数程序中所有必须的最小功能集。
如果用C语言编写CGI程序,则有很多选择,其中之一是`CGIC`库,来自[http://www.boutell.com/cgic/](http://www.boutell.com/cgic/)
## 更多信息
网上有大量的CGI资源。可以在Usenet组[comp.infosystems.www.authoring.cgi](news:comp.infosystems.www.authoring.cgi)和别人讨论CGI相关问题。HTML Writers Guild 的邮件列表是一个优秀的问题解答资源。更多资源在[http://www.hwg.org/lists/hwg-servers/](http://www.hwg.org/lists/hwg-servers/)
另外,还可以阅读CGI规范,其中有CGI程序操作的所有细节,原始版本见[NCSA](http://hoohoo.ncsa.uiuc.edu/cgi/interface.html) ,另有一个更新草案见[Common Gateway Interface RFC project](http://web.golux.com/coar/cgi/)
当你向一个邮件列表或者新闻组提交CGI相关问题时,你应该确保提供了足够的信息以更容易地发现并解决问题,诸如:发生了什么事、你希望得到什么结果、结果与你所期望的有什么出入、你运行的服务器、CGI程序是用什么语言编写的、如果可能就提供那个讨厌的代码。
注意,**不要**把CGI相关问题提交到Apache bug数据库,除非你坚信发现的是Apache源代码中的问题。
- Apache HTTP Server Version 2.2 文档 [最后更新:2006年3月21日]
- 版本说明
- 从1.3升级到2.0
- 从2.0升级到2.2
- Apache 2.2 新特性概述
- Apache 2.0 新特性概述
- The Apache License, Version 2.0
- 参考手册
- 编译与安装
- 启动Apache
- 停止和重启
- 配置文件
- 配置段(容器)
- 缓冲指南
- 服务器全局配置
- 日志文件
- 从URL到文件系统的映射
- 安全方面的提示
- 动态共享对象(DSO)支持
- 内容协商
- 自定义错误响应
- 地址和端口的绑定(Binding)
- 多路处理模块
- Apache的环境变量
- Apache处理器的使用
- 过滤器(Filter)
- suEXEC支持
- 性能方面的提示
- URL重写指南
- Apache虚拟主机文档
- 基于主机名的虚拟主机
- 基于IP地址的虚拟主机
- 大批量虚拟主机的动态配置
- 虚拟主机示例
- 深入研究虚拟主机的匹配
- 文件描述符限制
- 关于DNS和Apache
- 常见问题
- 经常问到的问题
- Apache的SSL/TLS加密
- SSL/TLS高强度加密:绪论
- SSL/TLS高强度加密:兼容性
- SSL/TLS高强度加密:如何...?
- SSL/TLS Strong Encryption: FAQ
- 如何.../指南
- 认证、授权、访问控制
- CGI动态页面
- 服务器端包含入门
- .htaccess文件
- 用户网站目录
- 针对特定平台的说明
- 在Microsoft Windows中使用Apache
- 在Microsoft Windows上编译Apache
- Using Apache With Novell NetWare
- Running a High-Performance Web Server on HPUX
- The Apache EBCDIC Port
- 服务器和支持程序
- httpd - Apache超文本传输协议服务器
- ab - Apache HTTP服务器性能测试工具
- apachectl - Apache HTTP服务器控制接口
- apxs - Apache 扩展工具
- configure - 配置源代码树
- dbmmanage - 管理DBM格式的用户认证文件
- htcacheclean - 清理磁盘缓冲区
- htdbm - 操作DBM密码数据库
- htdigest - 管理用于摘要认证的用户文件
- httxt2dbm - 生成RewriteMap指令使用的dbm文件
- htpasswd - 管理用于基本认证的用户文件
- logresolve - 解析Apache日志中的IP地址为主机名
- rotatelogs - 滚动Apache日志的管道日志程序
- suexec - 在执行外部程序之前切换用户
- 其他程序
- 杂项文档
- 与Apache相关的标准
- Apache模块
- 描述模块的术语
- 描述指令的术语
- Apache核心(Core)特性
- Apache MPM 公共指令
- Apache MPM beos
- Apache MPM event
- Apache MPM netware
- Apache MPM os2
- Apache MPM prefork
- Apache MPM winnt
- Apache MPM worker
- Apache模块 mod_actions
- Apache模块 mod_alias
- Apache模块 mod_asis
- Apache模块 mod_auth_basic
- Apache模块 mod_auth_digest
- Apache模块 mod_authn_alias
- Apache模块 mod_authn_anon
- Apache模块 mod_authn_dbd
- Apache模块 mod_authn_dbm
- Apache模块 mod_authn_default
- Apache模块 mod_authn_file
- Apache模块 mod_authnz_ldap
- Apache模块 mod_authz_dbm
- Apache模块 mod_authz_default
- Apache模块 mod_authz_groupfile
- Apache模块 mod_authz_host
- Apache模块 mod_authz_owner
- Apache模块 mod_authz_user
- Apache模块 mod_autoindex
- Apache模块 mod_cache
- Apache模块 mod_cern_meta
- Apache模块 mod_cgi
- Apache模块 mod_cgid
- Apache模块 mod_charset_lite
- Apache模块 mod_dav
- Apache模块 mod_dav_fs
- Apache模块 mod_dav_lock
- Apache模块 mod_dbd
- Apache模块 mod_deflate
- Apache模块 mod_dir
- Apache模块 mod_disk_cache
- Apache模块 mod_dumpio
- Apache模块 mod_echo
- Apache模块 mod_env
- Apache模块 mod_example
- Apache模块 mod_expires
- Apache模块 mod_ext_filter
- Apache模块 mod_file_cache
- Apache模块 mod_filter
- Apache模块 mod_headers
- Apache模块 mod_ident
- Apache模块 mod_imagemap
- Apache模块 mod_include
- Apache模块 mod_info
- Apache模块 mod_isapi
- Apache模块 mod_ldap
- Apache模块 mod_log_config
- Apache模块 mod_log_forensic
- Apache模块 mod_logio
- Apache模块 mod_mem_cache
- Apache模块 mod_mime
- Apache模块 mod_mime_magic
- Apache模块 mod_negotiation
- Apache模块 mod_nw_ssl
- Apache模块 mod_proxy
- Apache模块 mod_proxy_ajp
- Apache模块 mod_proxy_balancer
- Apache模块 mod_proxy_connect
- Apache模块 mod_proxy_ftp
- Apache模块 mod_proxy_http
- Apache模块 mod_rewrite
- Apache模块 mod_setenvif
- Apache模块 mod_so
- Apache模块 mod_speling
- Apache模块 mod_ssl
- Apache模块 mod_status
- Apache模块 mod_suexec
- Apache模块 mod_unique_id
- Apache模块 mod_userdir
- Apache模块 mod_usertrack
- Apache模块 mod_version
- Apache模块 mod_vhost_alias
- Developer Documentation for Apache 2.0
- Apache 1.3 API notes
- Debugging Memory Allocation in APR
- Documenting Apache 2.0
- Apache 2.0 Hook Functions
- Converting Modules from Apache 1.3 to Apache 2.0
- Request Processing in Apache 2.0
- How filters work in Apache 2.0
- Apache 2.0 Thread Safety Issues
- 词汇和索引
- 词汇表
- 指令索引
- 指令速查
- 模块索引
- 站点导航