3.4.1 CGI

CGI即通用网关接口(Common Gateway Interface),1993年由美国NCSA(National Center for Supercomputing Applications)发明。它具有简单易用、语言无关的特点。虽然今天已经少有人直接使用CGI进行编程,但它仍被主流的Web服务器,如Apache、IIS、Nginx1等,所广泛支持。另外,它还影响了其他一些服务器端技术,如PHP、Python WSGI、Ruby Rack等——在这些技术里你都能看到CGI的影子。所以它仍然值得学习。

先来看一个最简单的CGI程序2

#!/usr/bin/perl

print "Content-type: text/plain\n", "\n";
print "Hello, CGI!";

把这个程序保存到文件hello.pl,并假设它对应的URL是http://localhost/cgi-bin/hello.pl。在浏览器中访问这个URL,你就能看到程序的输出结果——一个普通文本:

Hello, CGI!

在前面HTTP - 服务器应答一节我们已经了解了HTTP应答的格式,对照一下,你会发现这个CGI程序的输出正是如此:首先是若干头部(header),一个一行(这里仅有一个Content-Type头);接着是一个空行(注意Content-Type这一行结尾输出了两个”\n”:第一个是头部的换行,第二个是分隔头部和正文的空行);最后是消息正文。与之前不同的是,这里我们没有输出应答状态行(Status Line)——这有一些特别:如果CGI程序没有指明,缺省的状态代码是200;如果想返回其他代码,需要用到一个特殊的Status头部(header),如:

print "Status: 404 Not found\n";

CGI程序可以动态产生任何内容,只要按照HTTP应答的格式向标准输出(stdout)设备输出这些内容即可。另外,CGI程序也可以由任何语言来编写,上面的例子只是以Perl为例,你还可以用Python、Ruby、BASH脚本……,以及编译好的C/C++程序。

在上面的例子中我们还没有谈到程序的输入(input)。CGI程序使用环境变量作为输入。下面的例子展示了这一点:

#!/usr/bin/perl

print "Content-type: text/plain\n", "\n";
foreach my $key (sort keys %ENV) {
  print "$key => $ENV{$key}\n";
}

这个CGI程序把它的环境变量和值都打印了出来。假设它对应的URL是http://localhost/cgi-bin/env.pl。我们在浏览器中访问http://localhost/cgi-bin/env.pl?name=Bob,会得到类似如下的结果:

GATEWAY_INTERFACE => CGI/1.1
HTTP_ACCEPT => text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
HTTP_ACCEPT_ENCODING => gzip, deflate, sdch
HTTP_ACCEPT_LANGUAGE => zh-CN,zh;q=0.8,en;q=0.6,ja;q=0.4
HTTP_CACHE_CONTROL => max-age=0
HTTP_CONNECTION => keep-alive
HTTP_COOKIE => ...
HTTP_HOST => localhost
HTTP_UPGRADE_INSECURE_REQUESTS => 1
HTTP_USER_AGENT => Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.86 Safari/537.36
PATH => /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
QUERY_STRING => name=Bob
REMOTE_ADDR => 10.0.0.9
REMOTE_PORT => 50497
REQUEST_METHOD => GET
REQUEST_SCHEME => http
REQUEST_URI => /cgi-bin/env.pl?name=Bob
SCRIPT_FILENAME => /var/www/cgi-bin/env.pl
SCRIPT_NAME => /cgi-bin/env.pl
SERVER_ADDR => 10.0.0.8
SERVER_ADMIN => webmaster@localhost
SERVER_NAME => localhost
SERVER_PORT => 80
SERVER_PROTOCOL => HTTP/1.1
SERVER_SIGNATURE => <address>Apache/2.4.7 (Ubuntu) Server at ubuntu-vm Port 80</address>
SERVER_SOFTWARE => Apache/2.4.7 (Ubuntu)
...

其中,HTTP_开头的变量都是请求头(request header),其他大部分都是服务器提供的元变量(meta variable),如SERVER_NAME,REQUEST_URI和REMOTE_ADDR等,以及少量关于主机的环境变量,如PATH。这些变量的含义大都不言自明,在此不一一解释。值得指出的是,我们在URL请求里的查询部分,即”?name=Bob”,可以通过QUERY_STRING变量得到。此外,如果请求含有消息正文(message body),如前面“资源与方法”一节的POST的例子,我们可以通过读取标准输入设备(stdin)来得到它。

总之,CGI程序通过环境变量和标准输入获得请求的各种参数信息,通过标准输出返回应答;服务器并不关心CGI程序是用什么语言编写的,它仅通过环境变量和标准输入、输出与CGI程序交互。

每当有一个请求对应到一个CGI程序时,服务器就启动一个进程执行这个CGI程序。因此CGI程序对主机的资源消耗比较大(想想如果有1000个并发请求会怎么样),同时它的响应速度也会比较慢(进程的启动比较花时间)。所以人们开始寻找CGI的替代者,这导致了FastCGI等技术的出现。关于CGI的更多信息,可参考https://en.wikipedia.org/wiki/Common_Gateway_Interface

1. 严格来说Nginx不直接支持CGI,但它支持CGI的变体FastCGI,所以实际上仍然算是支持的。参考:https://www.nginx.com/resources/wiki/start/topics/examples/simplecgi/
2. 以Apache服务器为例,请参考它的文档来设置好CGI的运行环境: http://httpd.apache.org/docs/current/howto/cgi.html

results matching ""

    No results matching ""