通俗易懂CGI

一直对CGI的概念就是客户端交由处理的具体请求事物的后台程序,看到这篇文章通俗易懂的把CGI讲解清楚,往后对自己写后台业务也会有更多理解。

文章转载自CGI

我们知道,服务端要处理用户的请求,必须通过编程语言来编写对应的处理逻辑,而你要做的不仅如此,你碰到的第一个问题就是如何接收用户的TCP请求并解析出HTTP协议格式。

OK,专业的事情交给专业的人来干。必然,我们应该用专业的Web Server(如Nginx、 Apache)来接收和解析TCP请求。而业务处理部分不可能会有开源的东西直接拿来用的,毕竟每一家公司的业务都是不同的,所以需要采用C、C++、Perl、Java、Node、Python等等编程语言来编码,并编译为一个可执行程序。(由于php等脚本语言是解释执行的,所以需要靠对应的php解释器来执行。)

说到这里,问题就来了,Web服务器如何与业务处理程序进行通信呢,以及其通信时传递数据的格式是怎么样的呢。这里就需要一套标准的协议,于是人们创造了CGI协议。 所以能处理CGI协议数据的业务处理程序,就被称为CGI程序。

Web Server和CGI程序通过环境变量、标准输入、标准输出进行通信。
协议与语言无关,只要一门语言支持标准输入、标准输入、读取环境变量,它就可以用来写CGI程序,就是说可以用bash、perl、c语言等来写。

环境变量中存放的是一次请求的相关信息,比如QUERY_STRING、
PATH_INFO、REMOTE_ADDR、REMOTE_HOST、CONTENT_TYPE、CONTENT_LENGTH、REQUEST_METHOD、SERVER_NAME等。 从标准输入读取的数据如表单中post过来的东西。

img

CGI

当客户端发出一个执行CGI程序的请求给服务器以后,服务器根据CGI的程序类别,决定某种数据传递方式,即服务器与CGI程序的沟通方式,这就是编写CGI程序的关键点。一般情况下,服务器与CGI程序之间是通过标准输入输出进行数据传递的。

这种通过标准输入输出来实现服务器与CGI之间的数据传递的标准,对于基于文件的UINX系统尤其适用

对服务器与CGI程序而言就是:服务器的标准输出与CGI程序的标准输入相连接,服务器标准输入与CGI的标准输出相连接。如此,就构成了一个完整的数据循环通路。

事实上,每个CGI程序的环境变量集在该CGI程序开始执行到终止,其对应的环境变量集就失效了,也就是对应环境变量集生存期与CGI程序是一致的.

工作流程

  1. 一个用户请求激活一个CGI应用程序;
  2. CGI应用程序将交互主页里用户输入信息提取出来;
  3. 将用户输入的信息传给服务器主机应用程序(如数据库查询〕;
  4. 将服务器处理结果通过HTML文件返回给用户;
  5. CGI进程结束。

优点

程序执行一次就退出,不需要关心资源释放,而是在每次处理完请求之后,由系统统一回收。

缺点

Fork-and-execute的模式,是的每次处理用户请求,都必须经历fork cgi进程、销毁cgi进程,一系列的I/O开销降低了网络的吞吐量

每一个客户端的连接都会启动一个CGI进程,在启动CGI进程过程中会不断重复去加载 CGI 脚本,整个过程还是很浪费系统资源,特别是在大并发的访问条件下,性能会很低下的

例:

Apache+CGI

配置Apache以允许CGI很简单。只需要在Apache的配置中,做一个脚本别名的配置:

1
ScriptAlias /cgi-bin/ /usr/local/apache/cgi-bin/

这样,所有请求/cgi-bin路径,都被Apache认为是CGI脚本,那么Apache就会去执行usr/local/apache/cgi-bin/这个路径下的程序了。比如,如果有URL为http://www.example.com/cgi-bin/test.cgi的请求,Apache会试图执行/usr/local/apache/cgi-bin/test.cgi文件并返回其输出。当然,这个文件必须存在而且可执行,并以特定的方法产生输出,否则Apache返回一个出错消息。

编写CGI程序

对于CGI模式,这个过程理解起来其实比较简单,所以我们编写的CGI程序也只需要能够进行输入输出流的读写即可。
比如甚至可以用bash来编写一个程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#!/bin/sh
# disable filename globbing
set -f
echo "Content-type: text/plain; charset=iso-8859-1"
echo
echo CGI/1.0 test script report:
echo
echo argc is $#. argv is "$*".
echo
echo SERVER_SOFTWARE = $SERVER_SOFTWARE
echo SERVER_NAME = $SERVER_NAME

不过下文讲到的FastCGI,由于需要持续运行保持一个死循环来接收新的请求,所以为了方便,C++程序中一般也要引入一个库。

FASTCGI

为了解决CGI的性能问题,出现了FastCGI,fastcgi则采用常驻进程的方式,在每个请求处理和结束的过程中,始终常驻在内存中,一旦激活,不需要花时间去fork,有效降低了前端请求应答的时延。在进行并发请求一个数据接口,或者请求一个文件的测试中,fastCGI的每个请求平均响应时间明显是更短,因此其吞吐量必然也更大。

FASTCGI原理,大概是我画的图这样的:
img

来看下Nginx里往FastCGI转发请求的配置:

1
2
3
4
5
6
7
8
9
pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
location ~ \.php$ {
root html;
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name;
include fastcgi_params;
}

可见,fastCGI确实是以Socket或UnixSocket的形式建立连接的。 其大概流程是,Web服务器接收到请求后,通过自身的FastCGI转发模块(如nginx的fastcgi_pass)跟fastCGI进程管理器建立连接,详细如下:

  1. Web 服务器启动时载入初始化FastCGI执行环境 。 例如 IIS ISAPIapache mod_fastcginginx ngx_http_fastcgi_modulelighttpd mod_fastcgi
  2. FastCGI进程管理器(例如跟Nginx配合的spawn_fcgi)自身初始化,启动多个CGI解释器进程并等待来自Web 服务器的连接。启动FastCGI进程时,可以配置以ip和UNIX 域socket两种方式启动。我们看到上面我的老博客的Nginx配置,是把请求转发给php-fpm具有地址和端口,那么php-fpm应该就是以socket的方式启动的。
  3. 当客户端请求到达Web 服务器时, Web 服务器将请求采用socket方式转发到 FastCGI主进程,FastCGI主进程选择并连接到一个CGI解释器(如果是C++写的就不是解释器了,而是C++的CGI程序)。Web 服务器将CGI环境变量和标准输入发送到FastCGI子进程。
  4. FastCGI子进程完成处理后将标准输出和错误信息从同一socket连接返回Web服务器。当FastCGI子进程关闭连接时,请求便处理完成。
  5. FastCGI子进程接着等待并处理来自Web 服务器的下一个连接。

由于 FastCGI 程序并不需要不断的产生新进程,可以大大降低服务器的压力并且产生较高的应用效率。它的速度效率最少要比CGI 技术提高 5 倍以上。它由于跟Web服务器之间通过socket这样的通信,所以还支持分布式的部署, 即 FastCGI程序可以在web 服务器以外的主机上执行。

Nginx+spawn-fcgi

这是Nginx一套nginx下配置fastcgi的常用组合,使用spawn-fcgi这个来让Nginx具有cgi的能力。

首先,安装nginx,安装的nginx自己就具有了转发cgi的能力。

1
sudo apt-get install nginx

但是,还得安装一个fastCGI进程管理器,他负责把CGI程序初始化起来,准备好,等待Web服务器的链接。所以需要执行:

1
sudo apt-get install spawn-fcgi

如果编写fastCGI的C++程序,还需要安装到系统上对应的库,在C++代码中进行引用。然后C++代码写成类似这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <iostream>
#include <fcgi_stdio.h>
#include <stdlib.h>
int main()
{
int count = 0;
while(FCGI_Accept() >= 0)
{
printf("Content-type: text/html\r\n"
"\r\n"
""
"FastCGI Hello!"
"Request number %d running on host%s "
"Process ID: %d\n",
++count,
getenv("SERVER_NAME"), getpid());
}
return 0;
}

与CGI程序的区别,就是主框架中有个循环:

1
2
3
4
// Initialize application;
while(FCGI_Accept() >= 0) {
Process request;
}

然后需要配置Nginx里的fastcgi转发:

1
2
3
4
5
6
location ~ \.cgi$ {
fastcgi_pass 127.0.0.1:9002;
fastcgi_index index.cgi;
fastcgi_param SCRIPT_FILENAME fcgi$fastcgi_script_name;
include fastcgi_params;
}

然后要启动spawn-fcgi:

1
spawn-fcgi -a 127.0.0.1:9002 -C 25 -f ./hello.cgi

apache+mod_fastcgi

通过mod_fcgid模块实现。这个模块曾属于第三方,但是在2009年被授予ASF,成为Apache的一个子项目。
mod_fcgid的安装参考mod_fcgid目录下的README-FCGID文件,里面包含具体安装方法,这里不在赘述

如果正常安装的话,会自动在apache配置文件中新增如下配置:

1
LoadModule fcgid_module modules/mod_fcgid.so

除了上面加载模块外,还需要在CGI运行目录加入如下配置项:

1
2
SetHandler fcgid-script
Options +ExecCGI

webserver——qzhttp

这个是公司内部的一个webserver,集成了fastcgi的功能。qzhttp支持fastcgi/cgi的模式,具有一系列成熟的监控模块,在fastcgi模式下,可以自适应的根据请求的数量来动态调整常驻进程的个数

FastCGI的缺点

采用fastcgi固然能够提高吞吐量,但是由于是常驻进程的模式,cgi版本变更发布相比apache+cgi的方式则显得不是那么灵活了。需要按照特定的方式灰度发布生效,后续将会进一步介绍

转载自:https://blog.cuiyongjian.com/fe/tencent-cgi/

坚持原创技术分享,您的支持将鼓励我继续创作!