Nginx支持PHP的PATHINFO模式配置分析

在上传php程序文件的时候可能会把apache的.htaccess文件上传上去 , 这样会影响nginx的配置 , 建议删除.htaccess文件在测试。php

ThinkPHP5发布了,最近也要基于ThinkPHP5作一个小项目,借着这个机会完全梳理下Nginx下有关pathinfo的配置。本文的宗旨是:远离咬文嚼字的理论,尽可能的通俗。因此不可避免的会出现遗漏和疏忽,敬请指教~html

Nginx的patinfo

CGI、FastCGI和PATHINFO

CGI

通用网关接口(Common Gateway Interface)是一个Web服务器主机提供信息服务的标准接口。经过CGI接口,Web服务器就可以获取客户端提交的信息,转交给服务器端的CGI程序进行处理,最后返回结果给客户端。nginx

神烦理论瞎,拿nginx、php这种模式来简单理解cgi更为直观:web

-------------正则表达式

nginx:“哎呀,收到客户端的一个http请求,该干活了......咦,有php-fpm这小子的活儿!”apache

nginx:“别睡了,别睡了,php-fpm你该起来干活儿了...”数组

php-fpm:“好滴,把客户端的http请求消息体给我一份啊......”服务器

php-fpm:“nginx,个人活儿干完了,接收我要发给客户端的数据,麻溜的...”网络

nginx:“好滴,合做愉快”框架

-------------

Nginx接收到php-fpm处理的结果后,就能够响应客户端的http请求给予一个回应了,客户端的这一次http请求就结束了,一张由php产生的华丽丽的网页就呈如今网民的面前。在这段对话中,nginx与php-fpm并无相互推诿扯皮,交流的很顺畅;没有推诿扯皮的缘由就是nginx与php-fpm之间的数据和消息传递使用了统一的标准格式,这个标准格式就是CGI,因此假若nginx和php-fpm中有任何一方不按CGI标准来玩,你推诿扯皮也没用。

发展到如今,对CGI的理解能够是一种标准接口(协议规范),也能够理解成处理动态网页的某种语言,好比:php、asp均可以宽泛的看作是一种cgi,这个时候cgi就被泛化了但依然包含了不推诿扯皮的交流标准的这一层含义。

FastCGI

FastCGI的Fast已经代表含义了,是一种快速的CGI,也是现代动态网页语言与web server之间广泛所采用的。FastCGI像是一个常驻型的CGI,它能够一直执行着,只要激活后,不会每次都要花费时间去fork一次(这是CGI最为人诟病的fork-and-execute 模式)。它还支持分布式的运算,即FastCGI程序能够在网站服务器之外的主机上执行而且接受来自其它网站服务器来的请求。

nginx与php-fpm就是采用的FastCGI模式。

PATHINFO

经常会见到这种格式的Urlhttp://blog.jjonline.cn/index.php/Article/Post/index.html ,这种Url理解有两种方式:

  • index.php当作一个目录看待:访问blog.jjonline.cn服务器根目录下的index.php目录下的Article目录下的Post目录下的index.html静态html文本文件;
  • index.php当作一个PHP脚本看待:访问blog.jjonline.cn服务器根目录下的index.php脚本,由该脚本产生html页面,Url中/Article/Post/index.html这一部分做为index.php脚本中使用的某种类型的参数。

绝大部分状况下,这种格式的Url理解方式是第二种,而/Article/Post/index.html这一部分理解成PATHINFO就行了。其实PATHINFO是一个CGI 1.1的一个标准,常常用来作为传参载体,只不过我们不必深刻。

因为Apache的默认配置文件开启了PATHINFO的支持,Apache+PHP的环境下PATHINFO格式的Url能够不出任何错误的执行正确路径的PHP脚本并在脚本中使用PATHINFO中的参数。而Nginx默认提供的有关执行php-fpm运行PHP脚本的默认配置文件中并无启用PATHINFO,从而致使了一个长久以来的误解:nginx不支持pathinfo。

早期版本的nginx确实不能直接支持pathinfo,但有变相的解决方法,网络上的一些配置nginx支持pathinfo的文章大多就是这种变相解决方法。nginx其实早已能够很简单的经过fastcgi_split_path_info指令支持pathinfo模式了,严格来讲是nginx的0.7.31以上版本就可使用这个指令了。

Nginx的PATHINFO配置

一、关于nginx配置指令的一些墨迹内容

默认的nginx是对http请求的uri进行正则匹配来决定这个请求是否要交给php-fpm来执行;nginx中有关是否要交给php-fpm这个cgi来解析执行某个php脚本的默认配置(nginx1.8.0)以下:

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$这段是一个正则匹配,被匹配的内容是http请求的uri,正则表达式就是\.php$,而~则是nginx的location指令中的一个标记符,表示这个location匹配uri采用正则表达式来匹配;在这里URI和URL仍是有区别,请厘清。正则表达式中$表示必须以某个字符或字符串结尾,这样上述默认配置中仅能匹配到以.php为结尾的uri交给php-fpm去解析,以下:

一、http://blog.jjonline.cn/index.php 匹配

二、http://blog.jjonline.cn/admin/index.php?m=Index&a=index 匹配,注意这里Url中有Get变量,nginx中location匹配的路径是uri,也就是虚拟路径部分,本例也就是:/admin/index.php

三、http://blog.jjonline.cn/admin/index.php/Index/index 不匹配,pathinfo模式,nginx将index.php理解成一个目录了,这种状况下的uri为:/admin/index.php/Index/index ,结尾并无.php这种条件

正确配置Nginx对php的pathinfo支持,先要理解清楚nginx配置文件中是如何将某个请求交给php-fpm来执行的,以上述配置段为例来分析一下:

root:这个指令配置了php脚本的根目录,可使用相对路径也可使用绝对路径,上述示例中是html,表示php的根目录在nginx安装目录下的html目录;这里的目录通常与nginx配置文件server段下的root目录一致,也就是web服务器的根目录;且大多数的时候建议使用绝对地址。假设这里的root设置为:/var/www/www.jjonline.cn/wwwRoot,这样网站根目录的绝对地址就是/var/www/www.jjonline.cn/wwwRoot,配合各类ftp服务器端配置,将ftp登陆的家目录设定为/var/www/www.jjonline.cn。拿ThinkPHP来举例:框架和核心模块文件能够放置在/var/www/www.jjonline.cn目录下,而入口文件放置在/var/www/www.jjonline.cn/wwwRoot下;这样框架和核心模块文件就不会被Url直接访问到。

fastcgi_pass:这个指令配置了fastcgi监听的端口,能够是TCP也能够是unix socket,这里通常推荐走TCP,这个TCP是由php-fpm配置文件决定的,再也不详细介绍。

fastcgi_index:这个指令配置了fastcgi的默认索引文件,与server端下index指令相似。

fastcgi_param:这个指令配置了fastcgi的一些参数,传递给php-fpm,这个指令是3段式,第一段fastcgi_param指令名称,第二段传递给php-fpm的参数的名称,第三段传递给php-fpm参数的值,也就是说fastcgi_param配置了一系列的key-value类型的值;对PHP来讲fastcgi_param指令产生的key-value键值对最后都(未确认,暂时这么理解吧~)转换成了超全局数组变量$_SERVER的键值对,上述示例中fastcgi_param  SCRIPT_FILENAME  /scripts$fastcgi_script_name就配置了一个SCRIPT_FILENAME的fastcgi参数,转换成PHP中的变量就是$_SERVER['SCRIPT_FILENAME'] ,PHP参考手册中对$_SERVER['SCRIPT_FILENAME']的说明是:“当前执行脚本的绝对路径”。对nginx来讲,将请求正确的交给php-fpm来执行正确的php脚本就是由fastcgi_param指令配置的SCRIPT_FILENAME来决定的,因此nginx能默契的与php-fpm协做,fastcgi_param指令正确的配置了SCRIPT_FILENAME值是关键。

include:这个指令将指定的文本文件的内容做为配置项包含进来,与php中的include差很少意思,这个指令的参数就是一个配置文件的路径,能够是相对路径也能够是绝对路径,路径中可使用通配符*;nginx的虚拟主机实现就使用到了这个指令,以及指令参数中使用到通配符。include fastcgi_params; 则表示将主配置文件目录下的fastcgi_params文本文件中的配置内容包含进来。读取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  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;

能够发现包含进来的fastcgi_params文件依然使用了fastcgi_param指令,配置了一大堆键值对,拿fastcgi_param SERVER_SOFTWARE nginx/$nginx_version;来简单分析下:SERVER_SOFTWARE$_SERVER['SERVER_SOFTWARE']对应,作后台管理系统经常会用到这个变量来显示服务器使用的软件,在php代码中读取出来的值就是nginx中这个地方配置的,这个时候PHP中$_SERVER['SERVER_SOFTWARE']读取出来的内容就是诸如nginx/1.8.0这样的字符串,这段nginx的配置中$nginx_version是nginx提供的一个变量,变量内容就是nginx版本号。

另外fastcgi_params文件与fastcgi.conf的内容是一摸同样的,任意包含一个便可,为何会有两个一摸同样的呢?这是nginx的开发者为不一样操做系统平台提供的,无需深究。

二、nginx支持pathinfo的本质和配置实现

依据上述第1条的墨迹得出两个结论:

一、nginx须要正确将请求交给php-fpm来执行php脚本,nginx先得正确分析出URI中是否要去请求某个PHP脚本;

二、当php-fpm正确执行某个PHP脚本后,PHP中pathinfo模式实现单一入口须要PHP中$_SERVER['PATH_INFO']包含了正确的pathinfo值;而PHP中的$_SERVER变量由nginx的fastcgi_param指令来决定;

因此让nginx支持pathinfo的配置中要修改内容也围绕这个两个点来展开。

第1、nginx的location能匹配到pathinfo格式的URI,去掉URI必须是.php结尾的限定,修改以下:

location  ~  \.php  {
    
}

第2、须要将URI进行正则切割,产生正确的PHP脚本文件路径和pathinfo值;

nginx的0.7.31以上版本之后就可使用fastcgi_split_path_info指令了,这个指令的参数为一个正则表达式,这个正则表示必须有两个捕获子组,从左往右捕获的第一子组自动赋值给nginx的$fastcgi_script_name变量,第二个捕获的子组自动赋值给nginx的$fastcgi_path_info变量。

一般状况下,也就是在没有使用fastcgi_split_path_info指令时nginx的$fastcgi_script_name变量保存着相对PHP脚本的URI,这个URI相对于web根目录就是实际PHP脚本的路径,因此下方的关于SCRIPT_FILENAME的配置很常见。

fastcgi_param  SCRIPT_FILENAME $document_root$fastcgi_script_name;

这样在高版本的nginx支持php的pathinfo配置就出来了,这种方式是正规且推荐的:其原理就是nginx正则分析好须要执行的PHP脚本路径和PATH_INFO变量。

##匹配nginx须要交给php-fpm执行的URI,先要容许pathinfo格式的URL可以被匹配到
##因此要去掉$
##nginx文档中的匹配规则为:^(.+\.php)(.*)$
##还有~ \.php这种写法 和 ~ \.php($|/)这种写法
##都是差很少意思没啥严格区别
##惟一区别就是有多个匹配php的location的话须要留意权重差别
location ~ ^(.+\.php)(.*)$ {
     root              /var/www/www.jjonline.cn/wwwRoot;
     fastcgi_pass   127.0.0.1:9000;
     fastcgi_index  index.php;
     ##增长 fastcgi_split_path_info指令,将URI匹配成PHP脚本的URI和pathinfo两个变量
     ##即$fastcgi_script_name 和$fastcgi_path_info
     fastcgi_split_path_info  ^(.+\.php)(.*)$;
     ##PHP中要能读取到pathinfo这个变量
     ##就要经过fastcgi_param指令将fastcgi_split_path_info指令匹配到的pathinfo部分赋值给PATH_INFO
     ##这样PHP中$_SERVER['PATH_INFO']才会存在值
     fastcgi_param PATH_INFO $fastcgi_path_info;
     ##在将这个请求的URI匹配完毕后,检查这个绝对地址的PHP脚本文件是否存在
     ##若是这个PHP脚本文件不存在就不用交给php-fpm来执行了
     ##否者页面将出现由php-fpm返回的:`File not found.`的提示
     if (!-e $document_root$fastcgi_script_name) {
         ##此处直接返回404错误
         ##你也能够rewrite 到新地址去,而后break;
         return 404;
     }
     fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
     include        fastcgi_params;
}

还有一种让nignx支持pathinfo的方式,这种方式须要PHP配置文件php.ini中开启cgi.fix_pathinfo配置项,赋值为1(php.ini中这个配置项的默认值就是1),早前这个配置项致使一个php任意文件解析的漏洞,见此:http://www.laruence.com/2010/05/20/1495.html,不过如今这个漏洞早已堵上,在我本机上测试,php-fpm将会直接返回403状态码和Access denied.的文字。

location ~ .php {
      root           /var/www/www.jjonline.cn/wwwRoot;
      fastcgi_pass   127.0.0.1:9000;
      fastcgi_index  index.php;
      ##先加载默认的fastcgi配置项
      include fastcgi_params;
      ##直接将网站根目录和完整的URI拼接起来后赋值给SCRIPT_FILENAME
      ##实际上此处赋值给SCRIPT_FILENAME的PHP脚本文件可能并不存在
      ##此处赋的值多是/var/www/www.jjonline.cn/index.php/Index/index形式
      fastcgi_param  SCRIPT_FILENAME $document_root$fastcgi_script_name;
      ##同时将完整的URI赋值给PATH_INFO,此处赋的值多是/index.php/Index/index形式
      fastcgi_param PATH_INFO $fastcgi_script_name;
}

因为php配置文件php.ini中的cgi.fix_pathinfo配置项处于开启状态,php-fpm接收到这些有问题的SCRIPT_FILENAME和PATH_INFO后会内部自动修正,因此这种状况在PHP代码中$_SERVER['SCRIPT_FILENAME']$_SERVER['PATH_INFO']是能够正确的修正解析的,这样配置nginx至关于把URI匹配出正确的SCRIPT_FILENAME和PATH_INFO值交给了php-fpm来执行,这种状况下你会发现PHP中存在$_SERVER['ORIG_SCRIPT_FILENAME']$_SERVER['ORIG_PATH_INFO']这两个变量,或许还存在$_SERVER['ORIG_SCRIPT_NAME']

最后将再也不推荐的配置方式贴出来,贴出来的目的是分析下配置原理,加深nginx的配置指令理解

##由于nginx中$fastcgi_script_name内建变量没法赋值
##全部经过设置$real_script_name这个自定义nginx变量来作中间值
location ~ \.php {
	root           /var/www/www.jjonline.cn/wwwRoot;
        fastcgi_pass   127.0.0.1:9000;
        fastcgi_index  index.php;
	##先加载默认的fastcgi配置项
	include        fastcgi_params;
	##正则解析路径,先使用set指令产生两个nginx变量并赋值
	##此处先将$path_info值赋值为空
	set $path_info "";
	set $real_script_name $fastcgi_script_name;
	##正则匹配URI,若能匹配将产生两个子组
	if ($fastcgi_script_name ~ "^(.+?\.php)(/.+)$") {
	    ##将两个子组赋值给刚生成的两个nginx变量
	    set $real_script_name $1;
	    set $path_info $2;
	}
	##将可能匹配到的$path_info值经过fastcgi_param指令设置进去
	fastcgi_param PATH_INFO       $path_info;
	fastcgi_param SCRIPT_FILENAME $document_root$real_script_name;
	##覆盖fastcgi_params文件中默认的SCRIPT_NAME配置项
	fastcgi_param SCRIPT_NAME     $real_script_name;
}

 

有时候会出现 Access denied 错误

  1. In your PHP-fpm www.conf set security.limit_extensions to .php or .php5 or whatever suits your environment. For some users, completely removing all values or setting it to FALSE was the only way to get it working.

  2. In your nginx config file set fastcgi_pass to your socket address (e.g. unix:/var/run/php-fpm/php-fpm.sock;) instead of your server address and port.

  3. Check your SCRIPT_FILENAME fastcgi param and set it according to the location of your files.

  4. In your nginx config file include fastcgi_split_path_info ^(.+\.php)(/.+)$; in the location block where all the other fastcgi params are defined.

  5. * In your php.ini set cgi.fix_pathinfo to 1

相关文章
相关标签/搜索