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
呢,因为服务器有Nginx
、Apache
、IIS
……,而运行在服务器上的处理请求的程序可能是用Java
、php
、C
、C++
……等各种语言写的。要在他们之前传输数据,就需要一个标准。
web服务器收到用户请求,就会把请求提交给cgi程序(如php-cgi),cgi程序根据请求提交的参数作应处理(解析php),然后输出标准的html语句,返回给web服服务器,WEB服务器再返回给客户端,这就是普通cgi的工作原理。
比如说是login
这种动态请求, 可能涉及用户名密码的验证、种cookie等,那么就需要有程序来处理用户登录这个动作。
关于CGI有更详细的一张图是:
CGI程序在每个web请求过来的时候,都会有启动和退出的过程,每个请求都会启动一个进程,也就是fork-and-execute
,这样在大规模请求的时候会非常慢。
FastCGI
FastCGI的优点:
前面说到CGI对每个web请求都会启动一个进程,这样的结果就是很慢。而FastCGI也是一种协议,可以看过是CGI的高级版,提高了CGI的性能,它通过一个进程/线程池,来实现long-live目的。FastCGI也是与语言无关的,其主要行为是将CGI解释器进程保持在内存中,不用每次都加载CGI解释器,并因此获得较高的性能。
下图以php为例:
对于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.conf
和fastcgi_param
这两份配置文件
看我上面的两个location配置,include了不同的文件,fastcgi.conf
和fastcgi_param
这两份配置文件一般在nginx/conf里面都会有。
但是fastcgi.conf
比fastcgi_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
配置
参考