动态web技术(二) --- CGI

本篇是系列文章中的第二篇,讲述大名鼎鼎的CGI技术。CGI 全称为Common Gateway Interface (通用网关接口),目的是能够让服务器能够方便的调用外部程序。CGI本身是一套协议和规范,原则上只要是拥有读写文件功能的编程语言都可以用来编写CGI程序,例如C,C++,Perl,Visual Basic,Shell等等,历史上用来编写CGI程序使用最广泛的是Perl语言,连PHP一开始也是用Perl编写的,估计也受这个传统的影响。服务器在认为这是一个CGI请求时,会调用相关CGI程序,并通过环境变量和标准输出将数据传送给CGI程序,CGI程序处理完数据,生成html,然后再通过标准输出将内容返回给服务器,服务器再将内容交给用户,CGI进程退出,在这个过程中,服务器的标准输出对应了CGI程序的标准输入,CGI程序的标准输出对应着服务器的标准输入,相当于利用两条管道建立了进程间的通信。

在今天的网站中,偶尔还能看到CGI的身影,比如Apache的下载页: http://httpd.apache.org/download.cgi





下面是用C编写的一个CGI小程序,向服务器返回数据只需要将数据写入标准输出即可,可见CGI程序的编写也是相当容易的:

cgi.c :

#include <stdio.h>

int main()
{
        char MimeType[]="text/html";
        fprintf(stdout, "Content-type: %s\r\n\r\n", MimeType);  //输出响应头,响应头之后要加两个"\r\n"
        fprintf(stdout, "<html><head><title>CGI小程序</title></head>\n");
        fprintf(stdout, "<body>由C编写的CGI小程序</body></html>\n");

        return 0;
}

由于Nginx不支持CGI(支持CGI的升级版FastCGI和SCGI),而Apache原生支持CGI,所以这里选用Apache来举例

首先要对Apache进行一定的配置,使之支持CGI程序,配置如下:


LoadModule cgi_module modules/mod_cgi.so  #注意这项配置是否已经存在,已存在就不要重复配置

AddHandler cgi-script .cgi    #设置cgi程序的扩展名,这里.cgi扩展名文件会被当作CGI来执行

#设置cgi-bin的目录权限,假设 /var/www/html 为你的DocumentRoot
<Directory "/var/www/html/cgi">
    AllowOverride None
    Options Indexes ExecCGI  # ExecCGI 表示该目录允许执行CGI,如果没有加这个权限,即使是.cgi也没有权限执行
    Order allow,deny
    Allow from all
</Directory>


重启Apache,把上面的C编写的CGI小程序用gcc编译成可执行文件cgi.out,并放到你配置的CGI目录,这里为 /var/www/html/cgi-bin


[root@localhost c]# gcc cgi.c -o test.cgi 
[root@localhost c]# mv test.cgi /var/www/html/cgi/


然后浏览器访问该文件 ,就可以看到输出结果了


从以上可以看出,cgi编程和普通的编程并没有太大的区别,cgi小程序也是可以直接运行的可执行文件,并且大多数编程语言都可以进行cgi编程,这或许也是cgi能够流行起来的原因之一,下面看用 php写一个CGI程序。

cgi.php :

#!/usr/bin/env php
<?php
// cgi.php
//由于php脚本不是可执行文件,这里用shell的方式来执行php脚本

fwrite(STDOUT, "Content-type: text/html\r\n\r\n");
fwrite(STDOUT, "<html><head></head><body><b>PHP编写的CGI程序演示 ". date("Y-m-d H:i:s") ."</b></body></html>\n");

给cgi.php 添加可执行权限

[root@localhost cgi]# chmod +x cgi.php 
[root@localhost cgi]# ./cgi.php 
COntent-type: text/html

<html><head></head><body><b>PHP编写的CGI程序演示 2017-06-23 15:41:00</b></body></html>


配置Apache支持.php扩展名的CGI小程序

AddHandler cgi-script .cgi .php    #设置cgi程序的扩展名,这里 .cgi 和 .php 扩展名文件会被当作CGI来执行

 重启Apache,访问该php文件:


可以看到CGI协议非常简单,但当前为止还没有涉及获取http请求的GET和POST参数,这就要说前面提到过的环境变量和CGI小程序的标准输入了,

下面修改前面的C和PHP小程序,使之支持读取GET和POST参数

cgi.c :

//cgi.c
#include <stdio.h>
#include <stdlib.h>
extern char **environ;  //调用环境变量
int main()
{

        char MimeType[]="text/html";
        fprintf(stdout, "Content-type: %s\r\n\r\n", MimeType);  //输出响应头,响应头之后要加两个"\r\n"
        fprintf(stdout, "<html><head><title>CGI小程序</title></head><body>\n");

        char **env;

        //循环输出环境变量
        for(env = environ; *env != NULL; env++)
        {
                fprintf(stdout, "%s<br>\n", *env);
        }

        char *query_string = getenv("QUERY_STRING"); //获取环境变量QUERY_STRING,也就是GET参数

        fprintf(stdout, "---------------<br>query_string:%s<br>-------------<br>", query_string);

        fprintf(stdout, "</body></html>\n");

        return 0;
}

再次编译之后浏览器访问:

http://127.0.0.1:8888/cgi/test.cgi?user=zyee&age=27

访问结果:

HTTP_HOST=127.0.0.1:8888
HTTP_CONNECTION=keep-alive
HTTP_CACHE_CONTROL=max-age=0
HTTP_ACCEPT=text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
HTTP_UPGRADE_INSECURE_REQUESTS=1
HTTP_USER_AGENT=Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36
HTTP_ACCEPT_ENCODING=gzip, deflate, sdch
HTTP_ACCEPT_LANGUAGE=zh-CN,zh;q=0.8,en;q=0.6
HTTP_COOKIE=UM_distinctid=15b130cb46b156-0f197c7d0f440a-3e64430f-1fa400-15b130cb46d3f2; CNZZDATA1258531050=1910116482-1490671422-%7C1490671422; _cnzz_CV1258531050=isLogin%7CLogout%7C1493265278109; CNZZDATA5201073=cnzz_eid%3D509423027-1490969081-http%253A%252F%252F127.0.0.1%253A801%252F%26ntime%3D1490969081; Hm_lvt_6bcd52f51e9b3dce32bec4a3997715ac=1496995397
PATH=/sbin:/usr/sbin:/bin:/usr/bin
SERVER_SIGNATURE=
Apache/2.2.15 (CentOS) Server at 127.0.0.1 Port 8888

SERVER_SOFTWARE=Apache/2.2.15 (CentOS)
SERVER_NAME=127.0.0.1
SERVER_ADDR=10.0.2.15
SERVER_PORT=8888
REMOTE_ADDR=10.0.2.2
DOCUMENT_ROOT=/var/www/html
SERVER_ADMIN=root@localhost
SCRIPT_FILENAME=/var/www/html/cgi/test.cgi
REMOTE_PORT=58373
GATEWAY_INTERFACE=CGI/1.1
SERVER_PROTOCOL=HTTP/1.1
REQUEST_METHOD=GET
QUERY_STRING=user=zyee&age=27
REQUEST_URI=/cgi/test.cgi?user=zyee&age=27
SCRIPT_NAME=/cgi/test.cgi
---------------
query_string:user=zyee&age=27
-------------


可以看到环境变量中包含了很多有用信息, 包括当前的URL,GET参数,客户端IP地址,请求头等等信息。

下面用PHP脚本来演示获取POST参数

cgi.php :


#!/usr/bin/env php
<?php
// cgi.php
//由于php脚本不是可执行文件,这里用shell脚本的方式来让php脚本可执行

$post = fread(STDIN, 1024);  // post参数从标准输入读取

fwrite(STDOUT, "COntent-type: text/html\r\n\r\n");
fwrite(STDOUT, "<html><head></head><body>");

fwrite(STDOUT, "\npost: $post\n");

fwrite(STDOUT, "<b>PHP编写的CGI程序演示 ". date("Y-m-d H:i:s") ."</b>");
fwrite(STDOUT, "</body></html>\n");
~                                          


curl模拟POST请求

[root@localhost cgi]# curl -d "name=zyee&age=27" "http://127.0.0.1/cgi/cgi.php"
<html><head></head><body>
post: name=zyee&age=27
<b>PHP编写的CGI程序演示 2017-06-23 17:25:26</b></body></html>


上传文件类型的POST请求  (  Multipart/form-data  ):

[root@localhost cgi]# curl -F "filename=@/var/www/html/cgi/test.cgi" "http://127.0.0.1/cgi/cgi.php"
<html><head></head><body>
post: ------------------------------1a7c7aacb288
Content-Disposition: form-data; name="filename"; filename="test.cgi"
Content-Type: application/octet-stream


<b>PHP编写的CGI程序演示 2017-06-23 19:32:08</b></body></html>

CGI编写对于有编程基础的人来说是相当容易上手的,那为什么又会被历史所遗弃呢,这就不得不说到CGI的运行方式问题了,每当一个CGI请求过来时,服务器会fork一个子进程来执行相应的CGI程序,当请求结束时,该CGI进程也随之结束,这样不停fork进程的开销是非常大的,这是造成CGI程序效率低下的主要原因。我们可以让CGI程序睡眠一段时间来观察这个过程,比如修改以上程序如下:


#include <stdio.h>
#include <unistd.h>


int main()
{
        sleep(10);  //睡眠10秒钟

        char MimeType[]="text/html";
        fprintf(stdout, "Content-type: %s\r\n\r\n", MimeType);  //输出文件类型

        fprintf(stdout, "<html><head><title>CGI小程序</title></head>\n");
        fprintf(stdout, "<body>由C编写的CGI小程序</body></html>");

        return 0;
}
                    

然后请求该页面,观察服务器进程信息,可以看到httpd进程fork出的一个子进程在执行CGI程序,并且请求结束该子进程便会退出




正是这种缺陷,所以Apache之后又推出了CGI模块的升级版 mod_cgid 模块,我们来看看官网对该模块的介绍:

Except for the optimizations and the additional ScriptSock directive noted below, mod_cgid behaves similarly to mod_cgiSee the mod_cgi summary for additional details about Apache and CGI.

On certain unix operating systems, forking a process from a multi-threaded server is a very expensive operation because the new process will replicate all the threads of the parent process. In order to avoid incurring this expense on each CGI invocation, mod_cgid creates an external daemon that is responsible for forking child processes to run CGI scripts. The main server communicates with this daemon using a unix domain socket.


大致意思: 

除了优化性能和增加了 ScriptSock指令外,mod_cgid 和 mod_cgi是非常类似的...

在当前的unix操作系统上,一个多线程的服务fork一个子进程代价是非常高昂的,因为fork出来的子进程会复制它父进程的所有线程。为了避免每次执行CGI程序都引起这个高代价的操作, mod_cgid模块创建一个外部的守护进程来负责创建子进程执行CGI程序,server和这个守护进程间通过unix domain socket来通信。


在后面我会向大家介绍一种更高级的技术,FastCGI 以及更高效的进程管理器 php-fpm 以及 Apache的 mod_fcgid模块,请大家关注后续文章。

That's it! :)

  • 7
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值