CGI

一个请求是如何被回应的,普通用户感知的如下图

浏览器和服务器

实服务器收到一个http请求之后,解析http请求,服务器负责解析HTTP请求,并把请求的参数*(如REQUEST_METHOD,PATH_INFO)*塞到进程的变量里面。然后调用相关程序,由程序产生内容。然后由服务器再加上适当的head,返回给浏览器。

解析了请求之后,以nginx为例,,会进入location模块匹配相应的规则。(location规则可以查阅我之前的笔记

比如说你请求静态文件*(如html等)*,那么可能的nginx location配置是这样的:

location ~ \.html$ {
	root /home/users/nemo/dir;
}

找不到相应文件的话,就会404。找到了这个文件就返回这个文件。浏览器会解析html,然后呈现在界面上。

但网站不可能只有静态的html,还需要有动态的内容或者交互,就需要有服务器上有程序能够产生动态内容。所以服务器就需要调用CGI程序获得你的项目的输出。

CGI,即common gateway inteface是服务器与请求处理程序之间的传输数据的标准。CGI可以用任何一种语言编写,只要这种语言具有标准输入、输出和环境变量。

为什么需要CGI呢,因为服务器有NginxApacheIIS……,而运行在服务器上的处理请求的程序可能是用JavaphpCC++……等各种语言写的。要在他们之前传输数据,就需要一个标准。

web服务器收到用户请求,就会把请求提交给cgi程序(如php-cgi),cgi程序根据请求提交的参数作应处理(解析php),然后输出标准的html语句,返回给web服服务器,WEB服务器再返回给客户端,这就是普通cgi的工作原理。

CGI架构图

比如说是login这种动态请求, 可能涉及用户名密码的验证、种cookie等,那么就需要有程序来处理用户登录这个动作。

关于CGI有更详细的一张图是:

CGI流程图

CGI程序在每个web请求过来的时候,都会有启动和退出的过程,每个请求都会启动一个进程,也就是fork-and-execute,这样在大规模请求的时候会非常慢。


FastCGI

FastCGI的优点:

前面说到CGI对每个web请求都会启动一个进程,这样的结果就是很慢。而FastCGI也是一种协议,可以看过是CGI的高级版,提高了CGI的性能,它通过一个进程/线程池,来实现long-live目的。FastCGI也是与语言无关的,其主要行为是将CGI解释器进程保持在内存中,不用每次都加载CGI解释器,并因此获得较高的性能。

FastCGI

下图以php为例:

FastCGI

对于php来讲,FastCGI比CGI好的地方在于,不用每次都解析php.ini、载入全部扩展等,这些只在进程启动的时候发生一次。

Fastcgi的工作原理是:会先启一个master,解析配置文件,初始化执行环境,然后再启动多个worker。当请求过来时,master会传递给一个worker,然后立即可以接受下一个请求。这样就避免了重复的劳动,效率自然是高。而且当worker不够用时,master可以根据配置预先启动几个worker等着;当然空闲worker太多时,也会停掉一些,这样就提高了性能,也节约了资源。

FastCGI的不足:

FastCGI的不足在于,比CGI消耗更多服务器内存。


PHP-FPM

FPM (FastCGI Process Manager),它是 FastCGI 的实现,任何实现了 FastCGI 协议的 Web Server 都能够与之通信。

而PHP-FPM是只适用于PHP的。一开始是由一个非官方的程序员开发出来的,从PHP5.4之后被纳入到官方源代码包里。

一开始是因为修改过php.ini之后,官方提供的PHP-CGI进程没办法平滑重启。所以有个程序员受不了了,就开发了PHP-FPM,作为代码补丁,能够实现平滑重启。

从PHP5.4之后PHP-FPM已经被纳入官方源代码里面,对修改过php.ini的处理机制是: 新的请求启用新的worker用新的配置,已经存在的正在处理请求的worker处理完任务之后就会被kill,来达到平滑过度的效果。

(如果你要给外界提供一个php写的web服务,不仅要启动服务器nginx,还要启动php-fpm。很多时候会忘了要启动php-fpm╮( ̄▽ ̄)╭。)


在服务器上,对于你的php项目,可能你的nginx location配置是这样的:

location / {
         root /home/users/nemo/project;
         fastcgi_index   index.php;
         fastcgi_pass    $php_upstream;
         include         fastcgi.conf;
}

或者是这样

location / {
     fastcgi_pass $php_upstream;
     fastcgi_param SCRIPT_FILENAME /home/users/nemo/project/index.php;
     include       fastcgi_params;
}

先说说fastcgi.conffastcgi_param这两份配置文件

看我上面的两个location配置,include了不同的文件,fastcgi.conffastcgi_param这两份配置文件一般在nginx/conf里面都会有。

但是fastcgi.conffastcgi_param多了一行fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;注意,$document_root$fastcgi_script_name之间没有斜杠/

原本Nginx只有fastcgi_param配置文件,后来发现很多人在定义「SCRIPT_FILENAME」时使用了硬编码的方式,于是为了规范用法便引入了fastcgi.conf

根据网上的这篇博客,里面提到了为什么不修改就配置文件的问题。

不过这样的话就产生一个疑问:为什么一定要引入一个新的配置文件,而不是修改旧的配置文件?这是因为「fastcgi_param」指令是数组型的,和普通指令相同的是:内层替换外层;和普通指令不同的是:当在同级多次使用的时候,是新增而不是替换。换句话说,如果在同级定义两次「SCRIPT_FILENAME」,那么它们都会被发送到后端,这可能会导致一些潜在的问题,为了避免此类情况,便引入了一个新的配置文件。

再来说说$document_root这个变量

$document_root这个是由root定义的

再来说说fastcgi_pass

fastcgi_pass表示设置FastCGI Server的地址,这个指令用于指定 fpm 进程监听的地址,Nginx 会把web请求发送到这个地址。这个指令用于指定 fpm 进程监听的地址,Nginx 会把web请求发送到这个地址。

(关于ip:port,这个可以在php-fpm.conf这个配置文件里面查看,一般默认是127.0.0.1:9000)

$php_upstream就是php-cgi.sock所在的路径,或者是ip:port的形式。

关于到底是选择TCP还是unix socket,两者各有权衡。根据这篇博客上所说

两种通信方式的分析和总结

从原理上来说,unix socket方式肯定要比tcp的方式快而且消耗资源少,因为socket之间在nginx和php-fpm的进程之间通信,而tcp需要经过本地回环驱动,还要申请临时端口和tcp相关资源。

当然还是从原理上来说,unix socket会显得不是那么稳定,当并发连接数爆发时,会产生大量的长时缓存,在没有面向连接协议支撑的情况下,大数据包很有可能就直接出错并不会返回异常。而TCP这样的面向连接的协议,多少可以保证通信的正确性和完整性。

如果是选择TCP,那么在php-fpm.conf里面,listen = 127.0.0.1:9000。如果是选择unix socket,那么在php-fpm.conf里面,listen = socket的路径

再来说说fastcgi_index

**fastcgi_index**的作用是,如果uri以/结尾,那么就把fastcgi_index的文件名追加到uri后面,这个值将存储在$fastcgi_script_name中。这个可以查看官方文档

举个具体的例子来说明fastcgi_index吧:

比如说nginx的location配置是这样的

location / {
         root /home/users/nemo/project;
         fastcgi_index   index.php;
         fastcgi_pass    $php_upstream;
         include         fastcgi.conf;
}

假如说请求是http://yoursite.com/tools/,那么$fastcgi_script_name这个变量里面的值就是/tools/index.php,那么SCRIPT_FILENAME的内容就是/home/users/nemo/project/tools/index.php

假如说请求是http://yoursite.com/admin.php,那么$fastcgi_script_name这个变量里面的值就是/admin.php,那么SCRIPT_FILENAME的内容就是/home/users/nemo/project/admin.php

**综上所示,**上面两个nginx location配置的功能就表示,使用某个路径或者是ip:port的php解析器来解析SCRIPT_FILENAME代表的的php脚本。


另外,看到这篇文章里面提到了PHP-FPM的一个漏洞,可能会导致php-fpm执行任意可执行文件。这篇文章讲得很清晰,吓得我赶紧去查了一下自己的php-fpm.conf配置文件里面的security.limit_extensions这项配置。

各位看官也可以关注一下自己的php-fpm.conf配置


参考

  1. https://www.zybuluo.com/phper/note/50231
  2. https://zh.wikipedia.org/wiki/FastCGI
  3. https://gist.github.com/rambolee/5384802eada67ffb4ef47cb15c50e9d8
  4. http://www.cnblogs.com/skynet/p/4173450.html