浅谈CGI基本原理和底层基本实现

历史来由:
早期的Web服务器,只能响应浏览器发来的HTTP静态资源的请求,并将存储在服务器中的静态资源返回给浏览器。随着Web技术的发展,逐渐出现了动态技术,但是Web服务器并不能够直接运行动态脚本,为了解决Web服务器与外部应用程序(CGI程序)之间数据互通,于是出现了CGI(Common Gateway Interface)通用网关接口。简单理解,可以认为CGI是Web服务器和运行其上的应用程序进行“交流”的一种约定。

简单地说CGI就是web服务器上的一种执行动态页面计算的外部逻辑计算扩展程序(webserver和cgi程序进行数据交互基于TCP协议)。

基本原理:
CGI的基本思路其实就是把标准输入(STDINT)、标准输出(STDOUT)、标准错误(STDERR)重定向到web server和cgi外部程序的tcp连接,进而直接从标准输入和进程环境变量中读取web server输入的数据,向stdout和stderror中写入参数,以此进行标准的数据交互。(CGI是Web服务器和一个独立的进程之间的协议,它会把HTTP请求Request的Header头设置成进程的环境变量,HTTP请求的Body正文设置成进程的标准输入,进程的标准输出设置为HTTP响应Response,包含Header头和Body正文。

实现思路:
利用Linux下的dup、dup2两个api复制文件描述符,实现重定向,这里只介绍stdout和环境变量,只有stedin和stderror同理,就重复说了:

#include<unistd.h>
int dup(int oldfd);
int dup2(int oldfd,int newfd);
   当调用dup函数时,内核在进程中创建一个新的文件描述符,此描述符是当前可用文件描述符的最小数值,这个文件描述符指向oldfd所拥有的文件表项。 
  dup2和dup的区别就是可以用newfd参数指定新描述符的数值,如果newfd已经打开,则先将其关闭。如果newfd等于oldfd,则dup2返回newfd, 而不关闭它。dup2函数返回的新文件描述符同样与参数oldfd共享同一文件表项。
  APUE用另外一个种方法说明了这个问题: 
  实际上,调用dup(oldfd)等效于,fcntl(oldfd, F_DUPFD, 0) 
  而调用dup2(oldfd, newfd)等效于,close(oldfd)fcntl(oldfd, F_DUPFD, newfd)
server关键代码
 int connfd = accept(sock,(struct sockaddr*)&client,&len);
 if(connfd < 0)
   printf("errno is:%d\n",errno);
 else 
 {
    
    
    close(STDOUT_FILENO);//关闭标准输出后dup返回的就是最小值stdout_fileno
    dup(connfd);
    printf("test cgi ret!\n");
 }

client端代码

 if((connect(sock,(struct sockaddr*)&client,sizeof(client)) != -1))
 {
    
    
  char buf[30];
  printf("connect successful!\n");
  if(recv(sock,buf,sizeof(buf),0) > 0)
  	cout<<buf<<endl;
 }

运行结果:
connect successful!
test cgi ret!

关于cgi获取输入的另一个方式就是webserver设置给cgi进程的进程环境变量,其实就是在cgi中通过getenv函数获取环境变量对应的值:

while (FCGI_Accept() >= 0)
{
    
    
//如果想得到数据,需要从stdin 去读,实际上从Nginx 上去读
//如果想上传数据,需要往stdout 写,实际上是给Nginx 写数据
printf("Content-type: text/html\r\n");
printf("\r\n");
printf("<title>Fast CGI Hello!</title>");
printf("<h1>Fast CGI Hello!</h1>\n");
//请求次数
printf("Request number %d <br>\n", ++count);
//SERVER_NAME:得到server 的host 名称
printf("SERVER_NAME:%s <br>\n", getenv("SERVER_NAME"));
//SERVER_PORT:得到server 的post 名称
printf("SERVER_PORT:%s <br>\n", getenv("SERVER_PORT"));
//获取客户端参数
printf("QUERY_STRING:%s <br>\n", getenv("QUERY_STRING"));
}

存在的优势和必要性:

必要性:早期的Web服务器,只能响应浏览器发来的HTTP静态资源的请求,并将存储在服务器中的静态资源返回给浏览器,无法实现动态计算页面,CGI通过外部扩展程序的方式,解决了http动态页面的问题

稳定性:
fastcgi 是以独立的进程池运行来 cgi,单独一个进程死掉,系统可以很轻易的丢弃,然后重新分配新的进程来运行逻辑

安全性:
fastcgi 和宿主的 server 完全独立, fastcgi 怎么 down 也不会把 server 搞垮

性能:
fastcgi 把动态逻辑的处理从 server 中分离出来, 大负荷的 IO 处理还是留给宿主 server, 这样宿主 server 可以一心一意作 IO,对于一个普通的动态网页来说, 逻辑处理可能只有一小部分, 大量的图片等静态 IO 处理完全不需要逻辑程序的参与

扩展性:
fastcgi 是一个中立的技术标准, 完全可以支持任何语言写的处理程序(php,java,python…)

语言无关性:
CGI 独立于任何语言的,CGI 程序可以用任何脚本语言或者是完全独立编程语言实现,只要这个语言可以在这个系统上运行。Unix shell script, Python, Ruby, PHP, perl, Tcl, C/C++, 和Visual Basic 都可以用来编写CGI 程序。

浏览器请求动态页面数据的整体基本流程:

  1. 通过 Internet 把用户请求送到 Web 服务器
    2)Web 服务器接收到用户请求并交给 CGI 程序
    3)CGI 程序把处理结果传送给 Web 服务器
    4)Web 服务器把结果送回到用户
    在这里插入图片描述

在这里插入图片描述
CGI 弊端
缺点: 一进程一请求的处理方式需要频繁的创建和销毁进程,导致web 服务器效率低、服务器系统资源占用大
在这里插入图片描述

fastcgi改进:
在这里插入图片描述
FastCGI
快速通用网关接口(FastCommonGatewayInterface/FastCGI)是通用网关接口(CGI)的改 进,描述了客户端和服务器程序之间传输数据的一种标准。FastCGI 致力于减少 Web 服务器 与 CGI 程式之间互动的开销,从而使服务器可以同时处理更多的 Web 请求。与为每个请求 创建一个新的进程不同,FastCGI 使用持续的进程来处理一连串的请求。这些进程由 FastCGI 进程管理器管理,而不是 web 服务器。

FastCGI 是什么?
FastCGI 是语言无关的、可伸缩架构的 CGI 开放扩展 其主要行为是将 CGI 解释器进程保持在内存中进行管理调度因此获得较高的性能

FastCGI 工作流程
在这里插入图片描述

  1. WebServer 启动时载入 FastCGI 进程管理器
  2. FastCGI 进程管理器自身初始化,启动多个 CGI 解释器进程 并等待来自 WebServer 的连 接
  3. 当客户端请求到达 WebServer 时,FastCGI 进程管理器选择并连接到一个 CGI 解释器 4) FastCGI 子进程完成处理后将标准输出和错误信息从同一连接返回 WebServer

FastCGI 协议
在 FastCGI 中,每一个 HTTP 请求(或者响应)消息都分为若干个记录(Record)进行传递, 每个 Record 又由头部(Header)和数据(Body)组成。
使用 Record 进行消息传递的好处:
• 多个请求的数据可以复用同一个连接进行传输,这样应用的实现就可以采用事件驱动的编 程模型或者多线程编程模型以提升效率;
• 同一个请求中的多个数据流的数据可以通过封装成不同记录的形式在同一个连接上传输, 例如 STDOUT 和 STDERR 两个输出流的数据可以通过同一个连接返回给 Web 服务器,而 不是不得不使用 2 个连接。

Record 结构体
typedef struct {
    
     /* Header */
unsigned char version; // FastCGI版本 
unsigned char type; // 当前 Record 的类型 
unsigned char requestIdB1; // 当前 Record对应的请求 id (通过 requestId 来识别请求) 
unsigned char requestIdB0; 
unsigned char contentLengthB1; // 当前 Record 中 Body 体数据的长度 
unsigned char contentLengthB0; 
unsigned char paddingLength; // Body 中填充块的长度 
unsigned char reserved; /* Body */ 
unsigned char contentData[contentLength]; // Body 体数据 
unsigned char paddingData[paddingLength]; // Body 中填充块长度 
} FCGI_Record;

FastCGI 协议类型
FastCGI 是二进制连续传递的,定义了一个统一结构的消息头,用来读取每个消息的消息体, 方便消息包的切割。一般情况下, 最先发送的是 FCGI_BEGIN_REQUEST 类型的消息,然后是 FCGI_PARAMS 和 FCGI_STDIN 类型 的消息, 当 FastCGI 响应处理完后,将发送 FCGI_STDOUT 和 FCGI_STDERR 类型的消息,最后以 FCGI_END_REQUEST 表示请求的结束。 FCGI_BEGIN_REQUEST 和 FCGI_END_REQUEST 分别表示请求的开始和结束,与整个协议相关

#define FCGI_BEGIN_REQUEST 1 //(web->fastcgi)请求开始数据包 
#define FCGI_ABORT_REQUEST 2 //(web->fastcgi)终止请求 
#define FCGI_END_REQUEST 3 //(fastcgi->web)请求结束 
#define FCGI_PARAMS 4 //(web->fastcgi)传递参数 
#define FCGI_STDIN 5 //(web->fastcgi)数据流传输数据 
#define FCGI_STDOUT 6 //(fastcgi->web)数据流传输数据 
#define FCGI_STDERR 7 //(fastcgi->web)数据流传输 
#define FCGI_DATA 8 //(web->fastcgi)数据流传输 
#define FCGI_GET_VALUES 9 //(web->fastcgi)查询 fastcgi 服务器性能参数 
#define FCGI_GET_VALUES_RESULT 10 //(fastcgi->web)fastcgi 性能参数查询返回 #define FCGI_UNKNOWN_TYPE 11 
#define FCGI_MAXTYPE (FCGI_UNKNOWN_TYPE)

**Web 服务器发送 FastCGI 请求时:**依次发送了 3 类 Record,类型分别为 BEGIN_REQUEST、PARAMS 和 STDIN FastCGI

**进程返回 FastCGI 响应时:**依次返回了 3 类 Record,类型分别为 STDOUT、STDERR、END_REQUEST

FastCGI 请求传递过程:

在这里插入图片描述
由 web 服务器向 FastCGI 程序传输的消息类型有以下几种:
FCGI_BEGIN_REQUEST 表示一个请求的开始,
FCGI_ABORT_REQUEST 表示服务器希望终止一个请求
FCGI_PARAMS 对应于 CGI 程序的环境变量,php$_SERVER 数组中的数据绝大 多数来自于此 FCGI_STDIN 对应 CGI 程序的标准输入,
FastCGI 程序从此消息获取 http 请求 的 POST 数据 此外
FCGI_DATA 和FCGI_GET_VALUES 这里不做介绍。

由 FastCGI 程序返回给 web 服务器的消息类型有以下几种:
FCGI_STDOUT 对应 CGI 程序的标准输出,web 服务器会把此消息当作 html 返 回给浏览器
FCGI_STDERR 对应 CGI 程序的标准错误输出, web 服务器会把此消息记录到 错误日志中
FCGI_END_REQUEST 表示该请求处理完毕
FCGI_UNKNOWN_TYPE FastCGI 程序无法解析该消息类型 此外还有 FCGI_GET_VALUES_RESULT 这里不做介绍

FastCGI 数据包格式
FastCGI 数据包两部分, 头部(header), 包体(body), 每个数据包都必须包含 header,body 可以 没有。header 为 8 个字节,body 必须为 8 的整数倍, 不是的话需要填充。

typedef struct {
    
     
unsigned char version; // 版本号 
unsigned char type; // 数据包类型 
unsigned char requestIdB1; // 记录 id 高 8 位 
unsigned char requestIdB0; // 记录 id 低 8 位 
unsigned char contentLengthB1; // 记录内容长度高 8 位(body 长度高 8 位) 
unsigned char contentLengthB0; // 记录内容长度低 8 位(body 长度低 8 位) 
unsigned char paddingLength; // 补齐位长度(body 补齐长度) 
unsigned char reserved; // 补齐位 
}FCGI_Header;

数据包包体
FCGI_BEGIN_REQUEST 类型记录的 contentData 数据部分的结构:

typedef struct {
    
     unsigned char roleB1; unsigned char roleB0; unsigned char flags; unsigned char reserved[5]; } FCGI_BeginRequestBody;
typedef struct {
    
     FCGI_Header header; FCGI_BeginRequestBody body; } FCGI_BeginRequestRecord;

FCGI_END_REQUEST 类型记录的 contentData 数据部分的结构:

typedef struct {
    
     
unsigned char appStatusB3; 
unsigned char appStatusB2; 
unsigned char appStatusB1; 
unsigned char appStatusB0; 
unsigned char protocolStatus; 
unsigned char reserved[3]; 
} FCGI_EndRequestBody;

typedef struct {
    
     
FCGI_Header header; 
FCGI_EndRequestBody body; 
} FCGI_EndRequestRecord;
typedef struct {
    
     
unsigned char type; 
unsigned char reserved[7]; 
} FCGI_UnknownTypeBody;

typedef struct {
    
     
FCGI_Header header; 
FCGI_UnknownTypeBody body; 
} FCGI_UnknownTypeRecord;

FastCGI 配置参数和环境变量
FastCGI 配置参数

Nginx 基于模块 ngx_http_fastcgi_module 实现通过 fastcgi 协议将指定的客户端请求转发至 spawn-fcgi 处理。

//转发请求到后端服务器,address 为后端的 fastcgi server 的地址,可用位置:location, if in location。 
fastcgi_pass address; 
//fastcgi默认的主页资源,示例:fastcgi_index index.html;这个功能和 index index.html 功能一样。 
fastcgi_index name; 
//设置传递给 FastCGI 服务器的参数值,可以是文本,变量或组合,可用于将 Nginx 的内置变量赋值给自定义 key fastcgi_param parameter value [if_not_empty]; //调用指定的缓存空间来缓存数据,可用位置:http, server, location;zone 的值为 keys_zone 定义好的缓存名称。 
fastcgi_cache zone|off; //定义用作缓存项的 key 的字符串,示例:fastcgi_cache_key $request_uri; 针对用户请求的 uri 进行缓存。 
fastcgi_cache_key string; 
//为哪些请求方法使用缓存。 
fastcgi_cache_methods GET|HEAD|POST...; 
//缓存空间中的缓存项在 inactive 定义的非活动时间内至少要被访问到此处所指定的次数方可被认作活动项,如果不够命中所指定的缓存次数为非活动项,会将被从缓存中清除。 fastcgi_cache_min_uses number; 
//收到后端服务器响应后,fastcgi 服务器是否关闭连接,建议启用长连接。这个功能需要开启,spawn 处理完这次请 求后,如果和 nginx 断开链接,下次用户再次发起请求,nginx 还需要和 spawn 先三次握手建立连接;开启之后,nginx 和 spawn 不断开连接,省略了建立连接的过程。 fastcgi_keep_conn on|off; 
//不同的响应码各自的缓存时长;200 的缓存时长需要长一些,404 的缓存时长短一些。 fastcgi_cache_valid 200 10m; 
//隐藏后端 spawn 服务器的响应头指定信息。 
fastcgi_hide_header field;

FastCGI 环境变量

SCRIPT_FILENAME $document_root$fastcgi_script_name;#脚本文件请求的路径 QUERY_STRING $query_string; #请求的参数;?app=123 
REQUEST_METHOD $request_method; #请求的动作(GET,POST) 
CONTENT_TYPE $content_type; #请求头中的 Content-Type 字段 
CONTENT_LENGTH $content_length; #请求头中的 Content-length 字段。
SCRIPT_NAME $fastcgi_script_name; #脚本名称 
REQUEST_URI $request_uri; #请求的地址不带参数 
DOCUMENT_URI $document_uri; #与$uri 相同。 
DOCUMENT_ROOT $document_root; #网站的根目录。在 server配置中 root 指令中指定的值 SERVER_PROTOCOL $server_protocol; #请求使用的协议,通常是 HTTP/1.0 或 HTTP/1.1。 GATEWAY_INTERFACE CGI/1.1;#cgi 版本 
SERVER_SOFTWARE nginx/$nginx_version;#nginx 版本号,可修改、隐藏 
REMOTE_ADDR $remote_addr; #客户端 IP REMOTE_PORT $remote_port; #客户端端口 SERVER_ADDR $server_addr; #服务器 IP 地址 SERVER_PORT $server_port; #服务器端口 SERVER_NAME $server_name; #服务器名,域名在 server 配置中指定的 server_name PATH_INFO $path_info;#可自定义变量

5. FastCGI 进程管理器
5.1FastCGI 进程管理器
fastcgi 可使用 spawn-fcgi 或者 php-fpm 来管理 (fastcgi 进程管理器,有很多不同类型的) nginx 下 fastcgi 与服务器是分离的
5.2spawn-fcgi
5.2.1spawn-fcgi 作用?

• 相当一个代理工具
• 角色完成 nginx 和 fastcgi 之间的进程间通信
5.2.2 环境配置
• nginx 处理不了的指令, 交给 fastcgi 处理
• 数据需要转发
• 数据需要发送到指定的端口
• 处理一个指令 test
• url:http://192.168.52.139/test

location /test{
    
    
 #配置 fastcgi 模块 
 fastcgi_pass 127.0.0.1:9001; 
 	#IP: 
		#127.0.0.1/localhost/192.168.52.139 
 	#端口: 
 		#将要处理的数据发送到 9001 端口 
 	#9001 端口对应一个进程, 该进程可以收到 nginx 发送过来的数据 
 include fastcgi.conf; 
 }

5.2.3spawn-fcgi 的使用
○ 编写一个 fcgi 程序 编译出来的程序名 test
○ spawn-fcgi-aIP-p 端口 -ffastcgi 程序 参数说明:
-a IP: 服务器 IP 地址
-p port: 服务器将数据发送到的端口
-f cgi 程序:spawn-fcgi 启动的可执行 fastcgi 程序

5.2.4fastCGI 协议、Spawn-fcgi、Nginx 三者关系
Nginx 是 web 服务器,只提供 HTTP 协议的输入和输出。
spawn-fcgi 服务器,只支持 Fastcgi 协议的输入和输出。
它们 2 者直接由 Nginx 将 HTTP 协议转换为 Fastcgi 协议传输给 fastCGI 进程处理

FastCGI、Spawn-fcgi、Nginx 三者关系
在这里插入图片描述
用户请求流程
在这里插入图片描述
6. http_fastcgi_module
nginx 服务支持 FastCGI 模式,能够快速高效地处理动态请求。 而 nginx 对应的 FastCGI 模块为 ngx_http_fastcgi_module。

ngx_http_fastcgi_module模块允许将请求传递给 FastCGI 服务器。 将 http 协议转为 fastcgi 协议

6.1ngx_http_fastcgi_module 模块配置

//address是 fastcgi server 监听的 IP 地址和端口; 
//示例:fastcgi_pass 127.0.0.1:9000; 
1. fastcgi_pass address;:指明反向代理的服务器
//示例:fastcgi_index index.php; 
2.fastcgi_index# ;:定义 fastcgi 应用的默认主页; 
//设定传递给后端 fastcgi server 参数及其值; //示例:fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name;
///index.php ---> /scripts/index.php
3.fastcgi_paramparameter value [if_not_empty];//定义缓存:缓存空间等;应用于 http 配置段。 
//------------------------------------------------------------------------------------------------| 
//--|path | 数据缓存在磁盘中位 置 | 
//--|levels=#[:#[:#]] | 定义的目录级别,levels=2:1表示两位十六进制字符命名目录,每个目录中还有目 录 | 
//--|keys_zone=name:size| 元数据缓存在内存中;name;cache 的标识符;size:元数据 cache 大 小; | 
//--|inactive=time | 缓存的非活动时 间 | 
//--|max_size | 缓存空间上 限 | 
//-----------------------------------------------------------------------------------------------4.fastcgi_cache_pathpath [levels=levels][use_temp_path=on|off] keys_zone=name:size [inactive=t ime][max_size=size];
//调用定义过的缓存; //zone即为通过 fastcgi_cache_path 定义缓存时其 keys_zone参数中的 name; 
3. fastcgi_cachezone | off;
//定义如何使用缓存键; 
//示例:fastcgi_cache_key $request_uri; 
4. fastcgi_cache_keystring;
//为何请求方法对应的请求进行缓存,默认为 GET 和 HEAD; 
5. fastcgi_cache_methods GET | HEAD | POST ...;
//缓存项的最少使用次数; 
6. fastcgi_cache_min_usesnumber;
//是否可使用 stale 缓存项响应用户请求; 
7. fastcgi_cache_use_staleerror | timeout | invalid_header | updating | http_500 | http_503 | http_403 |http_404 | off ...;
//对不同响应码的响应设定其可缓存时长; 10.fastcgi_cache_valid[code ...] time;

6.2ngx_http_fastcgi_module 更多信息
更多信息:http://nginx.org/en/docs/http/ngx_http_fastcgi_module.html
6.3nginx 配置
○ 添加 location location /group1/M00

location /group1/M00 {
    
     
	///home/milo/fastdfs/storage/fastdfs0/data - fastDFS 的 storage 存储数据的真实目录 
	root /home/milo/fastdfs/storage/fastdfs0/data; 
	//fastdfs 模块的名字 
	ngx_fastdfs_module; 
	}

○ 重新加载 nginx 的配置文件

6.3mod_fastdfs.conf 配置文件
一定参照当前存储节点 storage 的配置文件修改

○ log 日志目录 § base_path=/home/milo/fastDFS/storage 
○ 追踪器的地址 § tracker_server=192.168.52.139:22122 
○ 当前存储节点的端口 § storage_server_port=23000 
○ 当前存储节点所属的组 § group_name=group1 
○ 浏览器访问的时候, url 中是否包含组名 § url_have_group_name = true 
○ 当前存储节点存储路径的个数 § store_path_count=1 
○ 当前存储节点的存储路径 
	§ store_path0=/home/milo/fastDFS/storage/fastdfs0 
		□ 如果有多个, 需要全部写到配置文件中 
		® store_path1 
		® store_path2 
○ 整个的 fastDFS 文件系统一共有多少个组 § group_count = 1 
○ 每个组的信息 [group1] group_name=group1 storage_server_port=23000
store_path_count=1 
store_path0=/home/milo/fastDFS/storage/fastdfs0

猜你喜欢

转载自blog.csdn.net/wangrenhaioylj/article/details/108973076