总览
在使用常用的 LNMP 架构时,NGINX 和 PHP 通信时都是使用的 FastCGI 模式,什么是 CGI,应该如何理解其原理。
CGI
CGI(Common Gateway Interface,通用网关接口)是一个 Web 服务器提供信息服务的标准接口。通过 CGI 接口,Web 服务器就能够获取客户端提交的信息,然后转交给服务器端的 CGI 程序处理,每接收一个请求就会 Fork 一个新进程用于处理,然后将处理后的结果返回给客户端,处理完毕后关闭进程。这个恼人的 Fork-and-Execute 模式被人诟病,为解决此问题 FastCGI 应运而生。
小贴士:此方式下服务器有多少请求,就会有多少 CGI 子进程,每次进程启动都会加载解释器,载入配置文件,连接服务器等初始化操作,因此导致此方式性能低下,并且当请求数量多时会大量占用系统资源。
FastCGI
字面上就能看出来和 CGI 的区别是什么,很明显就是“快”,现代的接口都是使用的 FastCGI,抛弃了 CGI,并且 FastCGI 支持分布式工作,Web 服务可以将请求转发至其他服务器的 FastCGI 进行处理。
小贴士:此方式下服务器在启动 FastCGI 时会初始化环境,根据 Web 服务不同加载不同模块(Apache 的 mod_fastcgi;NGINX 的 ngx_http_fastcgi_module;iiS 的 iaspi 模块;Lighttpd 的 mod_fastcgi 模块等),此模式下父进程只启动一次,处理完毕后也不退出,父进程 Fork 出一定数量子进程监听在系统端口上一直等待请求的传输。
比如在 NGINX 配置中会自动将以 PHP 为后缀的请求转发至后端 CGI 接口进行处理
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;
}
PATHINFO
很多常见的 PHP 项目在不开启伪静态时都能看到请求的 URL 为类似形式,比如 https://www.vvave.net/index.php/admin/login.html
这种。
对于此类 URL 有两种理解方式:
- 服务器中存在
index.php
目录,子目录admin/
中存在一个login.html
文件。 - 将
index.php
识别为 PHP 脚本,后面的路径为传入的参数。
实际上绝大多数情况都是后一种情况,后方的部分为传入的参数,此部分就是所谓的 PATHINFO,具体说明详见 CGI。
由于 Apache 的默认配置文件开启了 PATHINFO 支持,Apache & PHP 的环境下 PATHINFO 格式的 URL 可以不出任何错误的执行正确路径的 PHP 程序并使用 PATHINFO 中的参数。而 NGINX 的默认配置中对 PATHINFO 的支持不完备,因此通过 fastcgi_split_path_info
参数进行实现。
深入
配置部分扩展
以 NGINX 的默认配置文件为例,默认的是对请求的 URL 进行正则匹配来决定这个请求是否要交给 FastCGI 来执行。
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;
}
- 关键字
location
是一个匹配标记,~ \.php$
被匹配的内容是请求的 URI 正则表达式就是\.php$
,而~
则是 NGX 独有的标识符,表示这个 location 匹配 uri 采用正则表达式来匹配;在这里 URI 和 URL 并不是同一个东西。正则表达式中$
表示以什么结尾。
综上,此段配置就是为匹配到以 .php 为结尾的 uri 交给 FastCGI 处理。
匹配符 | 匹配规则 | 优先级 |
---|---|---|
= | 表示精确匹配 | 1 |
^~ | 表示以某字符串开头 | 2 |
~ | 表示区分大小写的正则匹配 | 3 |
~* | 表示不区分大小写的正则匹配 | 4 |
!~ | 表示区分大小写不匹配的正则 | 5 |
!~* | 表示不区分大小写不匹配的正则 | 6 |
/ | 通用匹配,任何请求都会匹配到 | 7 |
注意:关于标识符,此处引用之前写过的博客内容,如上表。~
与~*
正则匹配规则在匹配后会寻找更精准的location
再次进行匹配。
举个例子:
URL | 是否匹配 |
---|---|
https://www.vvave.net/index.php | 匹配 |
https://www.vvave.net/admin/index.php?refer=www.vvave.net&a=index | 匹配 |
https://www.vvave.net/index.php/index/index | 不匹配 |
可以看出仅为 index.php 结尾的请求才能被此条规则正确匹配,其他请求会被认为是服务器上的目录。
- 关键字
root
为项目可读取的根目录,默认为相对路径的 html/ ,以常见的 ThinkPHP 项目为例,可将项目放置于/data/projects/www.domian.com/
下,然后将对外开放的目录 root 设为/data/projects/www.domian.com/public/
框架等文件可以放在上级目录../think/
或../framework/
中,这样就保护框架的核心组件不会对外开放。 - 关键字
fastcgi_pass
为 FastCGI 监听位置,可接收 TCP/IP 形式或 UNIXSOCKET 形式,本文不再赘述。 - 关键字
fastcgi_index
为 FastCGI 默认索引文件,与 server 关键字中的用法一致。 - 关键字
fastcgi_param
为 FastCGI 附加参数,此段配置为三段式配置,第一段为指令名;第二段为参数名;第三段为参数值。
注意:【此处引用晶晶博客中的说明便于理解】对 PHP 来说fastcgi_param
指令产生的参数配置转换成了超全局数组变量$_SERVER
的键值对配置,示例中fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name
就配置了一个SCRIPT_FILENAME
的fastcgi
参数,转换成 PHP 中的变量就是$_SERVER['SCRIPT_FILENAME']
,PHP 参考手册中对$_SERVER['SCRIPT_FILENAME']
参数说明为当前执行脚本的绝对路径。
- 关键字
index
为引入配置文件,与 C 语言中引入依赖的功能一致。实际上引入的配置文件中也是成堆的fastcgi_param
。
官方自带的 fastcgi_params 内容
fastcgi_param QUERY_STRING $query_string;
fastcgi_param REQUEST_METHOD $request_method;
fastcgi_param CONTENT_TYPE $content_type;
fastcgi_param CONTENT_LENGTH $content_length;
fastcgi_param SCRIPT_NAME $fastcgi_script_name;
fastcgi_param REQUEST_URI $request_uri;
fastcgi_param DOCUMENT_URI $document_uri;
fastcgi_param DOCUMENT_ROOT $document_root;
fastcgi_param SERVER_PROTOCOL $server_protocol;
fastcgi_param REQUEST_SCHEME $scheme;
fastcgi_param HTTPS $https if_not_empty;
fastcgi_param GATEWAY_INTERFACE CGI/1.1;
fastcgi_param SERVER_SOFTWARE nginx/$nginx_version;
fastcgi_param REMOTE_ADDR $remote_addr;
fastcgi_param REMOTE_PORT $remote_port;
fastcgi_param SERVER_ADDR $server_addr;
fastcgi_param SERVER_PORT $server_port;
fastcgi_param SERVER_NAME $server_name;
# PHP only, required if PHP was built with --enable-force-cgi-redirect
fastcgi_param REDIRECT_STATUS 200;
小贴士:此端内容都是为传递的参数,比如 NGINX 的版本、远程端口、服务协议、协议版本等参数,传递给 PHP 用于显示服务器信息。
配置原理
理解了 PATHINFO 后即可对其进行深层的理解和使用,比如部分项目的 URL 就需要配合 PATHINFO 的正确配置才能使用,举个例子:比如使用 ThinkPHP 框架开发的德尚商城项目就是如此。如果不添加正确的 PATHINFO 配置就会导致部分链接持续报错。
## 德尚商城配置模板
server {
listen 80;
server_name www.dsshop.com;
location / {
root /data/projects/dsshopsingle/public;
index index.php;
if (!-e $request_filename) {
rewrite ^(.*)$ /index.php?s=/$1 last;
break;
}
}
location ~ ^(.+\.php)(.*)$ {
root /data/projects/dsshopsingle/public;
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_split_path_info ^(.+\.php)(.*)$;
fastcgi_param PATH_INFO $fastcgi_path_info;
if (!-e $document_root$fastcgi_script_name) {
return 404;
}
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
}
指令理解
从上面的例子可以看到 fastcgi_split_path_info
实质上就是一个用于分割 URI 转换为参数以便正确传输给后端程序的一个指令。
此指令的参数为一个正则表达式,正则表达式必须要有两个捕获子组(可理解为匹配到内容),第一个子组会赋值给 $fastcgi_script_name
变量,第二个子组会赋值给 $fastcgi_path_info
变量。
也就是说在没有使用 PATHINFO 时,NGINX 的 $fastcgi_script_name
参数为 PHP 文件的路径(访问时的 URI),所以在实际项目中,一般不使用 NGINX 的自带配置文件中的 fastcgi_param
,而是如下的配置。
# 首先将 URI 中例如 xx.php/xxx/xxx 部分匹配出来
location ~ ^(.+\.php)(.*)$ {
root /projects/public/directory;
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
# 使用命令将 URI 进行正则拆分并赋值给不同参数
fastcgi_split_path_info ^(.+\.php)(.*)$;
# PHP 中要能读取到 PATHINFO 这个变量,就要通过 fastcgi_param 指令将 fastcgi_split_path_info 指令匹配到的 pathinfo 部分赋值给 PATH_INFO
fastcgi_param PATH_INFO $fastcgi_path_info;
# 在将这个请求的 URI 匹配完毕后,检查这个绝对地址的 PHP 文件是否存在
if (!-e $document_root$fastcgi_script_name) {
# 若不存在直接抛出 404
return 404;
}
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
小结
综上最后部分的配置基本可以完整套用,可以处理绝大多数情况,如果没有理解其工作原理也不想启用 PHP 的 cgi.fix_pathinfo
功能(可能存在安全风险),那么推荐可以直接抛弃 NGINX ,直接使用 Apache 即可,不存在 PATHINFO 问题。
附录
参考链接
本文由 柒 创作,采用 知识共享署名4.0
国际许可协议进行许可。
转载本站文章前请注明出处,文章作者保留所有权限。
最后编辑时间: 2019-10-15 17:15 PM