CGI

一、CGI 概述

最早的 Web服务器 只是简单地响应浏览器发来的 HTTP请求,并将存储在服务器上的 HTML文件 返回给浏览器,也就是 静态HTML。事物总是不断发展,网站也越来越复杂,所以出现 动态技术。但是服务器并不能直接运行 php,asp这样的文件,因此在服务器上运行一段程序,即 CGI程序。而服务器和 CGI程序做的约定,即“我给你请求参数,你根据条件处理,然后我接收你的处理结果再给客户端”,这个约定就是CGI (common gateway interface)。这个协议可以用vb,c,php,python 来实现。
这里写图片描述
而 CGI程序 运行在服务器之上,进行 文档和数据库操作。CGI 是 外部应用程序(CGI程序) 与 Web服务器 之间的接口标准,是在 CGI程序 和 Web服务器 之间传递信息的规范。Web服务器 使用 CGI 扩展自身的功能,通过CGI接口,Web服务器 就能够获取客户端提交的信息,转交给服务器端的 CGI程序 进行处理,最后返回结果给客户端。CGI使网页具有交互的功能。下面图可以看到流程:
这里写图片描述
CGI 应用程序的用途主要有以下几种:

  • 根据浏览者填写的 HTML 表单发送定制的答复;
  • 创建可单击的图像缩小图;
  • 创建一个浏览者可以搜索内容的数据库;
  • 提供服务器与数据库的接口,并把结果转换成 HTML 文档;
  • 制作动态 HTML 文挡。
二、Web 服务器与 CGI 程序交互

Web 服务器将根据 CGI 程序的类型决定数据向 CGI 程序的传送方式。一般情况下,服务器和CGI程序之间是通过 标准输入(STDIN) 和 标准输出(STDOUT) 来进行数据传递的,而这个过程需要环境变量的协作方可实现。
这里写图片描述

1、处理步骤
  1. 通过 Internet 把用户请求发送到 Web 服务器(网络通信);
  2. Web 服务器解析用户请求并且交给 CGI 程序处理(进程间通信);
  3. CGI 程序把处理结果传送给 Web 服务器;
  4. Web 服务器把结果传送给用户。

    • HTTP Server Accept到客户端请求
    • 服务器 Parse HTTP Request
    • 生成一个字符串数组,把相应的环境变量放进去,{ “REQUEST_METHOD=GET”, … }
    • fork 子进程,使用 exec 系列函数调用 CGI 程序,并要把相应的环境变量放进 exec 系列函数
    • 子进程的 stdout 直接就是 client socket
    • CGI 把 Response 直接写进 stdout,即 connfd
2、环境变量

除了标准输入输出之外,CGI 程序还通过环境变量来得到输入,操作系统提供了许多环境变量,它们定义了程序的执行环境,应用程序可以存取它们。Web 服务器和 CGI 接口又另外设置了一些环境变量,用来向 CGI 程序传递一些重要的参数

环境变量在CGI中有着重要的地位!每个CGI程序只处理一个用户请求,所以在激活一个CGI程序进程时也创建了属于该进程的环境变量。

对于 CGI 程序来说,它继承了系统的环境变量。CGI 环境变量在 CGI 程序启动时初始化,在结束时销毁。当一个 CGI 程序不是被 HTTP 服务器调用时,它的环境变量几乎是系统环境变量的复制。当这个 CGI 程序被 HTTP 服务器调用时,它的环境变量就会多了以下关于 HTTP 服务器、客户端、CGI 传输过程等项目。

与请求相关的环境变量
REQUEST_METHOD服务器与CGI程序之间的信息传输方式
QUERY_STRING采用GET时所传输的信息。
CONTENT_LENGTHSTDIO中的有效信息长度。采用POST时即是从标准输入STDIN中可以读到的有效数据的字节数。
CONTENT_TYPE指示所传来的信息的MIME类型。 目前,环境变量CONTENT_TYPE一般都是:application/x-www-form-urlencoded,表示数据来自于HTML表单。
CONTENT_FILE使用Windows HTTPd/WinCGI标准时,用来传送数据的文件名
PATH_INFO表示紧接在CGI程序名之后的其他路径信息。常作为CGI程序的参数出现。
PATH_TRANSLATEDCGI程序的完整路径名
SCRIPT_NAME所调用的CGI程序的名字
与服务器相关的环境变量
GATEWAY_INTERFACE服务器所实现的CGI版本
SERVER_NAME服务器的主机名、别名或IP地址
SERVER_PORT服务器的端口号
SERVER_SOFTWARE调用CGI程序的HTTP服务器的名称和版本号
与客户端相关的环境变量
REMOTE_ADDR发送请求的客户机的IP地址。它是Web客户机需要提供给Web服务器的唯一标识,可以在CGI程序中用它来区分不同的Web客户机。
REMOTE_HOST发送CGI请求的客户机的主机名
ACCEPT列出能被次请求接受的应答方式 内容如:”image/gif,image/jpeg”
ACCEPT_ENCODING列出客户机支持的编码方式
ACCEPT_LANGUAGE表明客户机可接受语言的ISO代码
AUTORIZATION表明被证实了的用户
FORM列出客户机的EMAIL地址
IF_MODIFIED_SINGCE当用GET方式请求并且只有当文档比指定日期更早时才返回数据
PRAGMA设定将来要用到的服务器代理
REFFERER指出连接到当前文档的文档的URL
USER_AGENT客户端浏览器的信息

CONTENT_TYPE:如application/x-www-form-urlencoded,表示数据来自HTML表单,并且经过了URL编码。

ACCEPT:客户机所支持的MIME类型清单,内容如:”image/gif,image/jpeg”

REQUEST_METHOD:它的值一般包括两种:POST 和 GET,但我们写 CGI 程序时,最后还要考虑其他的情况。

  • 环境变量是一个保存用户信息的内存区。当客户端的用户通过浏览器发出 CGI 请求时,服务器就寻找本地的相应 CGI 程序并执行它。在执行 CGI 程序的同时,服务器把该用户的信息保存到环境变量里。接下来,CGI程序的执行流程是这样的:查询与该CGI程序进程相应的环境变量:第一步是request_method,如果是POST,就从环境变量的len,然后到该进程相应的标准输入取出len长的数据。如果是GET,用户数据就在环境变量的QUERY_STRING里。
  • setenv来设置环境变量啊,环境变量的值是先在服务器程序通过setenv设置,才能在CGI程序中getenv得到。要不然不会存入特定的字符串。
  • 环境变量是通过 exec 函数族的一个参数传递给子进程的,不会设置到系统环境变量。其实环境变量本来就是 per process 的,只不过有“继承性”。
三、GET 与 POST
1、GET 方法

在该方法下,CGI 程序无法直接从服务器的标准输入中获取数据,因为服务器把它从标准输入接收到得数据编码到环境变量QUERY_STRING(或PATH_INFO)。

处理 GET 表单时,服务器通过环境变量给 CGI 程序发送数据,并且 GET 的表单内容存放在 QUERY_STRING 中。因此,在服务器与 CGI 程序通信之前,需要服务器先把 GET 表单进行解析,并建立环境变量。 同时,由于 CGI 程序是通过标准输出向服务器输出数据,因此在通信之前,还需要建立一条服务器与 CGI 程序之间的通信管道,重定向 CGI 程序的标准输出到服务器的管道读端口(也可能直接重定向到 connfd)。如下图所示:
这里写图片描述

2、POST 方法

客户端用 POST 方式发送数据有一个相应的MIME类型(通用 Internet 邮件扩充服务:Multi-purpose Internet Mail Extensions)。目前,MIME 类型一般是:application/x-wwww-form-urlencoded,该类型表示数据来自HTML表单。该类型记录在环境变量 CONTENT_TYPE 中,CGI 程序应该检查该环境变量的值。

处理 CGI POST 表单时,客户端来的用户数据通过写管道输入到 CGI 的标准输入,同时将用户数据的长度赋予环境变量中的 CONTENT_LENGTH,同时 CGI 程序输出数据时通过标准输出发送到服务器。 因此,在服务器向 CGI 程序传送数据的时候,除了建立环境变量,还需要建立两个管道,并分别把 CGI 程序的标准输入与标准输出进行重定向。即:

  • 在第一条管道中,服务器往管道写端写数据,管道的另一端读端重定向到 CGI 程序的标准输入, CGI 程序通过标准输入读数据;
  • 在第二条管道中,把管道的写端重定向到 CGI 程序的标准输出, CGI 程序通过标准输出向服务器发送数据,服务器通过管道的读端读取 CGI 发送过来的数据。

具体过程如下图所示:
这里写图片描述

如果采用POST方法,那么客户端来的用户数据将存放在CGI进程的标准输入中,用户数据的长度赋予在环境变量中的CONTENT_LENGTH。

客户端用POST方式发送数据有一个相应的MIME类型(通用Internet邮件扩充服务:Multi-purpose Internet Mail Extensions)。目前,MIME类型一般是:application/x-wwww-form-urlencoded,该类型表示数据来自HTML表单且经过 URL 编码。该类型记录在环境变量CONTENT_TYPE中,CGI程序应该检查该变量的值。

3、GET 与 POST 的区别

事实上 get 适用于多数请求,而保留 post仅用于更新站点。根据 HTTP 规范,get 用于信息获取,而且应该是安全的和幂等的。所谓安全的意味着该操作用于获取信息而非修改信息。换句话说,get 请求一般不应产生副作用。幂等的意味着对同一 URL的多个请求应该返回同样的结果。完整的定义并不像看起来那样严格。从根本上讲,其目标是当用户打开一个链接时,它可以确信从自身的角度来看没有改变资源。比如,新闻站点的头版不断更新。虽然第二次请求会返回不同的一批新闻,该操作仍然被认为是安全的和幂等的,因为它总是返回当前的新闻。反之亦然。post请求可能改变服务器上的资源的请求。仍然以新闻站点为例,读者对文章的注解应该通过 post请求实现,因为在注解提交之后站点已经不同了(比方说文章下面出现一条注解)。

GET 和 POST 的区别主要如下:

  • 采用 GET 方法提交 HTML 表单数据的时候,客户机将把这些数据附加到由 ACTION 标记命名的 URL 的末尾,用一个包括把经过 URL 编码后的信息与 CGI 程序的名字分开:http://www.mycorp.com/hello.html?name=hgq$id=1。其中 QUERY_STRING 的值为 name=hgq&id=1
  • 以 GET 方式接收的数据是有长度限制,而用 POST 方式接收的数据是没有长度限制的。并且,以 GET 方式发送数据,可以通过URL 的形式来发送,但 POST 方式发送的数据必须要通过 Form 才到发送。
  • GET 安全性非常低,POST 安全性较高。

有些程序员不愿意采用 GET 方法,因为在他们看来,把动态信息附加在 URL 的末尾有违 URL 的出发点:URL 作为一种标准用语,一般是用作网络资源的唯一定位标示。

四、CGI 实现
1、从服务器获取数据

C语言实现代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int get_inputs()
{
int length;
char *method;
char *inputstring;

method = getenv(“REQUEST_METHOD”);  //获取“REQUEST_METHOD”环境变量,并将返回结果赋予指针
if(method == NULL)
    return 1;       //找不到环境变量REQUEST_METHOD
if(!strcmp(method, ”POST”)) // POST方法
{   //数据从标准输入读取,数据长度由CONTENT_LENGTH确定
    length = atoi(getenv(“CONTENT_LENGTH”));    //结果是字符,需要转换
    if(length != 0)
    {
        inputstring = (char*)malloc(sizeof(char)*length+1) //必须申请缓存,因为stdin是不带缓存的
        fread(inputstring,sizeof(char),length,stdin); //从标准输入读取一定数据(stdin已重定向)
    }
}
else if(!strcmp(method, “GET”))
{   //数据在QUERY_STRING中
    Inputstring = getenv(“QUERY_STRING”);
    length = strlen(inputstring);
}
if(length == 0)
    return 0;
}

获取环境变量的时候,如果先判断“REQUEST_METHOD”是否存在,程序会更健壮,否则在某些情况下可能会造成程序崩溃。因为假若CGI程序不是由服务器调用的,那么环境变量集里自然就没有与CGI相关的环境变量(如REQUEST_METHOD,REMOTE_ADDR等),也就是说 getenv(“REQUEST_METHOD”) 将返回NULL!

2、URL 编码

不管是POST还是GET方式,客户端浏览器发送给服务器的数据都不是原始的用户数据,而是经过URL编码的。此时,CGI的环境变量Content_type将被设置,如Content_type = application/x-www-form-urlencode就表示服务器收到的是经过URL编码的包含有HTML表单变量数据。

编码的基本规则是

  • 变量之间用 “&” 分开;变量与其对应值用 “=” 连接;
  • 空格用 “+” 代替;保留的控制字符则用 “%” 连接对应的16禁止ASCII码代替;某些具有特殊意义的字符也用 “%” 接对应的16进制ASCII码代替;
  • 空格是非法字符;任意不可打印的 ASCII 控制字符均为非法字符。

例如,假设3个HTML表单变量filename、e-mail和comments,它们的值对应分别为hello、mike@hotmail.com和 I’ll be there for you,则经过URL编码后应为:

filename=hello&e-mail=hello@hotmail.com&comments=I%27ll+be+there+for+you

所以,CGI程序从标准输入或环境变量中获取客户端数据后,还需要进行解码。解码的过程就是 URL 编码的逆过程:根据 “&” 和 “=” 分离HTML表单变量,以及特殊字符的替换

在解码方面,PYTHON代码实现是最理想的,cgi.FieldStorage()函数在获取数据的同时就已自动进行代码转换了,无需程序员再进行额外的代码编写。Perl其次,因为在一个现成的Perl库:cgi-lib.pl中提供了ReadParse函数,用它来进行URL解码很简单。C 即需要对字符串解析,依次处理。

//Perl
require ‘cgi-lib.pl’;
&ReadParse(*input);
3、CGI 数据输出

CGI 响应的规则

  • CGI 必须响应 至少一行头 + 换行 + 响应内容;
  • 解释器响应文档时,必须要有 CONTENT-TYPE 头;
  • 在客户端重定向时,解释器除了 client-redir-response = 绝对url地址,不能再有其他返回,然后服务器返回一个 302 状态码;
  • 解释器响应三位数字状态码;
  • 服务器必须将所有解释器返回的数据响应给客户端,除非需要压缩等编码,服务器不能修改响应数据;

CGI 程序如何将信息处理结果返回给客户端?这实际上是 CGI 格式化输出。

在 CGI 程序中的标准输出stdout是经过重定义了的,它并没有在服务器上产生任何的输出内容,而是被重定向到客户浏览器,这与它是由C,还是Perl或Python实现无关。所以,我们可以用打印来实现客户端新的 HTML 页面的生成。比如,C 的printf是向该进程的标准输出发送数据,Perl 和 Python 用print向该进程的标准输出发送数据。

(1)CGI 标题

CGI 的格式输出内容必须组织成标题/内容的形式。CGI 标准规定了 CGI 程序可以使用的三个 HTTP 标题。标题必须占据第一行输出!而且随后必须带有一个空行。

标题描述
Content_type(内容类型)设定随后输出数据所用的MIME类型
Location(地址)设定输出为另外一个文档(URL)
Status(状态)指定HTTP状态码

MIME

向标准输出发送网页内容时要遵守 MIME 格式规则:任意输出前面必须有一个用于定义 MIME 类型的输出内容行(Content-type),而且随后还必须跟一个空行。如果遗漏了这一条,服务将会返回一个错误信息。

例如Perl和Python:

print “Content-type:text/html\n\n”;   //输出HTML格式的数据
print “<body>welcome<br>”
print “</body>”

C语言:

printf( “Content-type:text/html\n\n”);
printf(“Welcome\n”);

MIME 类型以类型/子类型(type/subtype) 的形式表示。

其中 type 表示几种典型文件格式的一种:Text、Audio、Video、Image、Application、Mutipart、Message。

Subtype则用来描述具体所用的数据格式。

Application/msword微软的Word文件
Application/octet-stream一种通用的二进制文件格式
Application/zipZip压缩文件
Application/pdfPdf文件
…………

Location

使用 Location 标题,一个 CGI 可以使当前用户转而访问同一服务器上的另外一个程序,甚至可以访问另外一个URL,但服务器对他们的处理方式不一样。

使用 Location 的格式为:Location:Filename/URL,例如:

print “Location:/test.html\n\n”;

这与直接链接到test.html的效果是一样的。

print “Location:http://www.chinaunix.com/\n\n“

由于该URL并不指向当前服务器,用户浏览器并不会直接链接到指定的URL,而是给用户输出提示信息。

HTTP状态码

表示了请求的结果状态,是CGI程序通过服务器用来通知用户其请求是否成功执行的信息码。

五、CGI中的信号量和文件锁

​ 因为CGI程序时公用的,而Web服务器都支持多进程运行,因此可能会发生同时有多个用户访问同一个CGI程序的情况。比如,有2个用户几乎同时访问同一个CGI程序,服务器为他们创建了2个CGI程序进程,设为进程A和进程B。假如进程A首先打开了某个文件,然后由于某种原因被挂起(一般是由于操作系统的进程调度);而就在进程A被挂起的这段时间内,进程B完成了对文件的整个操作流程:打开,写入,关闭;进程A再继续往下执行,但进程A所操作的文件依旧是原来文件的就版本,此时进程A的操作结果将覆盖进程B的操作结果。

为了防止这种情况发生,需要用到文件锁或者信号量。

钥匙文件?

假如有多个不同的HTML可以调用同一个CGI程序,那么CGI程序如何区分它们呢?一个是通过隐含的INPUT标签。不过觉得这个比较麻烦,因为CGI必须经过一系列解码后才能找到这个隐含INPUT的变量和其值。

六、设置HTTP服务器以兼容CGI

​ 用Perl编写的CGI程序后缀为:.pl;Python编写的CGI程序后缀为:.py;而C编写的CGI程序后缀为:.cgi,如果在win下编译出来的是.exe,最好将它重命名为.cgi。这些都是为了HTTP服务能够识别并调用它们。

​ 当使用appche httpd服务器时,请编辑它的配置文件httpd.conf如下:

​ 修改AddHandler cgi-script一句为AddHandler cgi-script .cgi .py.pl

七、关于CGI的C语言库——cgihtml

​ Cgihtml是一个应用非常广泛的C语言编写的CGI库。它提供的功能函数如下:

​ Read_cgi_input():获取并解析HTML表单输入,返回一个指向某结构体的指针

​ Cgi_val():获取每个表单变量的值

​ Html_header():输出HTML标题栏

​ Html_begin():输出HTML文档的开始部分

​ H1():输出一行字符,字体为H1

Html_end():输出HTML文档的结尾部分。

#include “cgi-lib.h”

#include “html-lib.h”

八、CGI 与 FastCGI

CGI 工作原理:CGI是一种标准,并不限定语言。所以Java、PHP、Python都可以通过这种方式来生成动态网页。但是实际上这些动态语言却很少这样用。原来是CGI有一大硬伤。那就是每次CGI请求,那么Apache都有启动一个进程去执行这个CGI程序,即颇具Unix特色的fork-and-execute。可是当访问量增大,并发存在,这个fork-and-execute的操作会严重拖慢Server的进程,于是就有了 FastCGI。而Java的Servlet技术则是一种常驻内存的技术,不会频繁的发生进程上下文的创建和销毁操作。

FastCGI:简单来说,其本质就是一个常驻内存的进程池技术,由调度器负责将传递过来的CGI请求发送给处理CGI的handler进程来处理。在一个请求处理完成之后,该处理进程不销毁,继续等待下一个请求的到来。不会每次都要花费时间去 fork 一次(这是 CGI 最为人诟病的 fork-and-execute 模式)。

一般情况下,FastCGI的整个工作流程是这样的:

1.Web Server 启动时载入 FastCGI 进程管理器(IIS ISAPI或Apache Module)

2.FastCGI 进程管理器自身初始化,启动多个 CGI 解释器进程(可见多个php-cgi)并等待来自 Web Server 的连接。

3.当客户端请求到达 Web Server 时,FastCGI 进程管理器选择并连接到一个 CGI 解释器。 Web server 将 CGI 环境变量和标准输入发送到 FastCGI 子进程 php-cgi。

4.FastCGI 子进程完成处理后将标准输出和错误信息从同一连接返回 Web Server。当 FastCGI 子进程关闭连接时, 请求便告处理完成。FastCGI 子进程接着等待并处理来自 FastCGI 进程管理器(运行在 Web Server 中)的下一个连接。 在 CGI 模式中,php-cgi 在此便退出了。

九、Java Servlet与CGI比较

与传统的CGI和许多其他类似CGI的技术相比,Java Servlet具有更高的效率,更容易使用,功能更强大,具有更好的可移植性,更节省投资。在未来的技术发展过程中,Servlet有可能彻底取代CGI。

在传统的CGI中,每个请求都要启动一个新的进程,如果CGI程序本身的执行时间较短,启动进程所需要的开销很可能反而超过实际执行时间。而在Servlet中,每个请求由一个轻量级的Java线程处理(而不是重量级的操作系统进程)。

在传统CGI中,如果有N个并发的对同一CGI程序的请求,则该CGI程序的代码在内存中重复装载了N次;而对于Servlet,处理请求的是N个线程,只需要一份Servlet类代码。在性能优化方面,Servlet也比CGI有着更多的选择。

十、JavaScript与CGI比较

有的人认为可以用JavaScript来代替CGI程序,这其实是一个概念上的错误。JavaScript只能够在客户浏览器中运行,而CGI却是工作在服务器上的。他们所做的工作有一些交集,比如表单数据验证一类的,但是JavaScript是绝对无法取代CGI的。但可以这样说,如果一项工作即能够用JavaScript来做,又可以用CGI来做,那么绝对要使用JavaScript,在执行的速度上,JavaScript比CGI有着先天的优势。只有那些在客户端解决不了的问题,比如和某个远程数据库交互,这时就应该使用CGI了。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值