通用网关接口(Common Gateway Interface/CGI)是一种重要的互联网技术,可以让一个客户端,从网页浏览器向执行在网络服务器上的程序请求数据。CGI描述了服务器和请求处理程序之间传输数据的一种标准。最初,CGI是在1993年由美国国家超级电脑应用中心(NCSA)为NCSA HTTPd Web服务器开发的。这个Web服务器使用了UNIX shell 环境变量来保存从Web服务器传递出去的参数,然后生成一个运行CGI的独立的进程。CGI程序可以用任何脚本语言或者是完全独立编程语言实现,只要这个语言可以在这个系统上运行。像Unix shell script, Python, Ruby, PHP, Tcl, C/C++,和Visual Basic都可以用来编写CGI程序。(摘自维基百科)
在第1段中所提到的“请求处理程序”就是CGI程序。
既然这么多编程语言都可以编写CGI程序,我们不妨也来试试,举个栗子:(假设你对linux和web环境有一定的了解,那我们就用bash shell来编写CGI程序)
# !/bin/sh
echo -e "Content-Type:text/html\n\n"
echo "hello CGI"
echo -e 参数是处理特殊字符,会把"\n"以换行符进行输出。
问题:为什么要输出"Content-Type:text/html\n\n"?
答:在RFC3875中规定:"script MUST supply a Content-Type field in the response" 脚本响应时必须提供Content-Type字段。"\n\n"是分割HTTP协议中header与body的。
每一次请求都会生成一个运行CGI的独立的进程,既然是独立进程,使用linux中的"ps"命令就可以查看得到,我们来做一个实验,"逮住"这个进程。
# !/bin/sh
sleep 20
echo -e "Content-Type:text/html\n\n"
echo "hello CGI"
"sleep 20":"睡眠"20秒,方便我们查看CGI程序的进程
# ps -ef | grep index.sh
UID PID PPID C STIME TTY TIME CMD
www 19848 13642 0 21:55 ? 00:00:00 /bin/sh /opt/web/cgi-bin/index.cgi
# ps -ef | grep httpd
UID PID PPID C STIME TTY TIME CMD
root 13598 1 0 Jul30 ? 00:00:16 /usr/local/apache2/bin/httpd -f /usr/local/apache2/conf/httpd.conf
www 13642 13598 0 21:53 ? 00:00:00 /usr/local/apache2/bin/httpd -f /usr/local/apache2/conf/httpd.conf
有一个很"巧合"的现象:CGI程序进程的父ID(PPID)与Apache的子进程(这里指的worker进程)ID(PID)一样。 其实这个CGI程序进程是由Apache的子进程fork出来的,然后使用了exec系列函数执行的CGI程序,所以CGI程序需要x(执行)权限。
补充一下:测试用的Apache是2.4版本,多处理模块(MPM)选用的worker,可能Apache使用了一些其他技术(比如线程技术)来完成以上工作,大致上原理是这样的。例如 Boa Webserver fork出来一个子进程后,使用了execve函数执行的CGI程序,并且将上一个程序的环境变量传入。
CGI程序是通过临时环境变量获取HTTP详情(在第1段中也提到过)。在web server执行CGI程序时,将环境变量传入或者继承上一个程序的,在CGI1.1标准中,环境变量如下:
变量名 | 描述 (以下描述均摘自互联网,仅供参考,英文好的同学可以直接阅读RFC3875) |
---|---|
AUTH_TYPE | 服务器验证用户身份所使用的机制 |
CONTENT_LENGTH | 假如REQUEST_METHOD为POST,这个环境变量的值是标准输入中可读取到的有效数据的字节数 |
CONTENT_TYPE | 所传递来的信息的MIME类型 |
GATEWAY_INTERFACE | 服务器遵守的CGI版本 |
PATH_INFO | CGI程序的附加路径 |
PATH_TRANSLATED | PATH_INFO对应的绝对路径 |
QUERY_STRING | GET请求参数 |
REMOTE_ADDR | 发送请求的客户机的IP地址 |
REMOTE_HOST | 递交脚本的主机名 |
REMOTE_IDENT | 如果Web服务器是在ident (一种确认用户连接你的协议)运行, 递交表单的系统也在运行ident, 这个变量就含有ident返回值 |
REMOTE_USER | 递交脚本的用户名. 如果服务器的Authentication被激活,这个值可以设置。 |
REQUEST_METHOD | 请求方式:GET、POST |
SCRIPT_NAME | 指向CGI脚本的路径 |
SERVER_NAME | 服务器的IP或名字 |
SERVER_PORT | 主机的端口号 |
SERVER_PROTOCOL | 服务器运行的HTTP协议 |
SERVER_SOFTWARE | 服务器软件的名字 |
写个程序验证一下:
# !/bin/sh
echo -e "Content-Type:text/html\n\n"
echo $GATEWAY_INTERFACE
请求,输出结果为: CGI/1.1
-
GET
当GET请求时,REQUEST_METHOD 为 GET;QUERY_STRING 为请求的参数和值,例如:
http://www.example.org/cgi-bin/demo.cgi/?name=cgi&version=1.1
QUERY_STRING 值为 name=cgi&version=1.1 -
POST
POST数据不会存到QUERY_STRING环境变量中,而是从标准输入(STDIN)中获取数据, 具体是如何将请求数据重导向到标准输入(STDIN)的,这涉及到了管道,大概是使用了dup2函数,由于知识有限,本文不再做详解。
以下是读取POST数据的示例
bash shell
#!/bin/sh echo -e "Content-Type:text/html\n\n" echo $(</dev/stdin)
php
#!/usr/local/php5.6/bin/php <?php echo "Content-Type:text/html\n\n"; readfile('php://stdin'); ?>
注:图中数据返回是通过标准输出(STDOUT),然后将数据重导向。具体实现涉及到了管道和dup2函数,本文不做详解。
谢谢阅读! 如有错误或者不好的地方麻烦指出。共同学习,共同进步~
(完)