CGI的出现
CGI算是一门古老的技术,它是在上个世纪90年代出现的,在当时Web只有静态网站的情况下提出了动态网站的需求,CGI技术就是为了满足这种需求而出现的。
当时Web站点只有静态文件,静态网页是没有变化的,而如果需要服务器能够能够拓展更多的功能,例如表单处理,访问数据库并处理返回的数据,制作动态网站等等,那么就需要一种程序语言编写相应的功能,但这种程序如何与静态文件html以及服务器交互,共同完成一份HTTP响应报文给浏览器?这就是CGI的作用。
CGI
通用网关接口(Common Gateway Interface, CGI)是Web服务器运行时外部程序的规范,利用CGI编写的程序可以扩展服务器功能。CGI 应用程序能与浏览器进行交互,还可通过数据API与数据库服务器等外部数据源进行通信,从数据库服务器中获取数据。格式化为HTML文档后,发送给浏览器,也可以将从浏览器获得的数据放到数据库中。几乎所有服务器都支持CGI,编写CGI的语言可以是C、Java、Perl、Python等。
-
“通用”:只要是支持获取环境变量和输入输出的语言都能用来编写CGI程序。
-
“网关”:网关具有两个不同实体(协议、程序等)之间的转换功能。
-
“接口”:两个实体是通过接口来实现数据交互,CGI的接口是环境变量和标准输入输出,即HTTP服务器往其中写入或读取数据,CGI程序也是往其中读取或写入数据。
CGI通信模型
我把这个模型分为三个阶段。
准备阶段
当浏览器发送一个CGI程序的URL请求时,URL的域名部分被定位到服务器地址,服务器接收到请求后,解析URL的路径部分,并定位到文件系统中相应的文件,服务器发现这是一个CGI程序,服务器就为它创建一个进程,并解析HTTP报文中的数据,例如表单或查询字符串,然后将其往进程(PCB)中写入环境变量和标准输入中。
如果是GET请求,URL的query部分被解析后,将其写入到QUERY_SRING的环境变量中。
如果是POST请求,就将报文体中的数据写入到标准输入中。
执行阶段
当服务器为CGI程序创建好一个进程并初始化变量后,CGI程序开始执行,前面说过CGI程序可以是C、Python、Java等语言编写,那么CGI定义所说的“服务器扩展功能”也是用这些编程语言实现,例如在python实现的CGI程序中,先用cgi模块读取环境变量和标准输入,然后再调用mysql模块访问MySQL数据库,然后再对返回的数据做进一步处理。
又如定义所说的“动态网站”也体现在这里,例如CGI程序可以利用从数据库得到的数据,对静态文件HTML做修改。
输出阶段
事实上,文件就是数据的集合,静态网站中服务器返回一个HTML文件,返回的是文件中的数据,所以用CGI实现的动态网站中,也是返回数据,只不过返回的数据可能有所变化。CGI程序最后在标准输出中输出数据,服务器把这些数据重定向到socket中,最终返回给浏览器。
环境变量
当一个CGI程序不是被HTTP服务器调用时,它的环境变量几乎时系统环境变量的复制。
当一个CGI程序被HTTP服务器调用时,它的环境变量会增加关于HTTP服务器、客户端、HTTP请求等相关项。
可以分为三类:与请求相关的、与服务器相关的、与客户端相关的。
列举一部分环境变量:
变量名 | 含义 |
---|---|
REQUEST_METHOD | HTTP请求方法 |
QUERY_STRING | 采用GET方式时URL的查询字符串 |
CONTENT_LENTGH | stdio中有效信息长度(即请求报文体中的数据长度) |
更多环境变量可在网上查找资料。
URL编码
不管是POST或GET方式,浏览器发送给服务器的数据都不是原生的字符数据,而是经过URL编码的。HTTP请求的URL部分是URL编码的,而表单数据也是经过URL编码的,这时"Content-Type"的值为"application/x-www-form-urlencode"(MIME类型)。
URL编码规则:
(1)变量之间用"&"分开;
(2)变量与其对应的值用"="连接;
(3)空格用"+"代替;
(4)保留的控制字符用"%"拼接对应的16进制ASCII码代替;
(5)某些非打印字符也可以用"%"拼接对应的16进制ASCII码代替;
(6)空格是非法字符;
在CGI程序从标准输入或环境变量中读取变量中,某些语言有相应的模块或函数,例如python:
import cgi
form = cgi.FieldStorage()
name = form.getvalue("username") # username为变量名
CGI输出
CGI程序的标准输出stdout被服务器重定向到socket中,所以CGI程序可以用"print"输出函数生成的新的HTML页面。但需注意的是,这里的CGI输出的数据将会是HTTP响应报文的数据,而不仅仅是响应报文体部分的数据,因此HTTP响应的header部分也需要"print"输出。
CGI提供三个响应头部:
header | 描述 |
---|---|
Content-type | 输出数据的MIME类型 |
Locaiton | URL重定向地址 |
Status | HTTP状态码 |
python程序实例:
import cgi
form = cgi.FieldStorage()
name = form.getvalue("username")
print("Content-Type: text/html;charset=utf-8")
print("") # HTTP头部与体部分之间的一个空白行
print("<h1>%s</h1>" % name)
配置Apache使用CGI
很多服务器支持CGI,包括Apache,接下来配置Apache使用CGI:
(1)打开httpd.conf配置文件,找到CGI脚本的目录配置:
#
# "${SRVROOT}/cgi-bin" should be changed to whatever your ScriptAliased
# CGI directory exists, if you have that configured.
#
<Directory "${SRVROOT}/cgi-bin">
AllowOverride None
Options None
Require all granted
</Directory>
(2)修改CGI脚本的根目录路径,并做如下配置:
<Directory "D:/phpStudy/WWW/webpy">
AllowOverride None
Options +ExecCGI
Order allow,deny
Allow from all
</Directory>
(3)修改"Addhandler",这样一来,.py和.cgi文件被访问时,就会当作程序执行。
AddHandler cgi-script .cgi .py
CGI程序
不同语言编写的CGI程序后缀名,C语言使用.cgi,python使用.py,Perl使用.pl。
C语言实现的一个CGI程序:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int get_inputs(){
int length;
char *method;
char *inputstring;
method = getenv("REQUEST_METHOD");
if(method == NULL)
return 1;
if(!strcmp(method, "POST")){
// 读取保准输入的数据
length = atoi(getenv("CONTENT-LENGTH"));
if(length != 0){
inputstring = malloc(sizeof(char)*length + 1);
fread(inputstring, sizeof(char), length, stdin);
}
} else if(!strcmp(method, "GET")){
// 读取URL的query部分
inputstring = getenv("QUERY_STRING");
length = strlen(inputstring);
}
if(length == 0)
return 0;
}
我曾试图用python的CGI开发一个小型的项目管理系统,但实现的过程非常繁琐,还要重新造一堆轮子,而且这些轮子还需要自己重新组装,整个过程非常痛苦,而且开发效率还很低下,自那以后深刻地领悟到框架真的是个好东西。
一个python实现的CGI程序:
#!T:\Python38\python.exe
#coding: utf-8
import sys
import cgi
import cgitb
import codecs
# Python3.6 及以后版本中文乱码解决
sys.stdout = codecs.getwriter('utf-8')(sys.stdout.buffer)
# CGI模块
# 创建FieldStorage实例化
form = cgi.FieldStorage()
# # 获取数据
site_name = form.getvalue("username")
site_url = form.getvalue("password")
print("Content-type:text/html")
print()
print("<html>")
print("<head>")
print("<meta charset=\"utf-8\">")
print("<title>菜鸟教程 CGI 测试实例</title>")
print("</head>")
print("<body>")
print("<h2>%s官网:%s</h2>" % (site_name, site_url))
print("</body>")
print("</html>")
访问编写的CGI程序的时候经常遇到 "500 内部错误"的问题,根据我的经验,可能的原因是:
(1)python脚本语法错误或者其它执行异常;
(2)python脚本中没有"print"输出,服务器没有得到CGI程序返回的数据,它就认为脚本的执行发生了错误,响应500;
如果你用CGI实现一些只是阅读文章的Web网站倒是足够了,但我们知道HTTP是一个无状态协议,而COOKIE可以存储用户的状态,想要完成一个有登录功能的Web网站,还需要引入COOKIE模块,python3的cookie模块是http.CookieJar。
在从http请求的cookie头部中获取数据时,还需要判断环境变量"HTTP_COOKIE"是否存在,因此要导入os模块,并用下面语句判断:
import os
if "HTTP_COOKIE" in os.environ:
pass
CGI的问题
CGI会影响Web服务器的性能,因为每一次访问CGI程序,都会创建一个进程,这需要占用CPU的时间,大量的CGI进程会消耗很多系统资源,导致服务器性能下降。除此之外,还有多进程同步访问文件的问题。因此,种种不方便处理的问题导致CGI技术现在已经很少使用了。
资料: