最完美 ThinkPHP(tp) 的 Nginx 配置文件
前言
作为一个成熟的框架,对于多种服务器环境,应该提供虚拟主机标准配置样例的,但 ThinkPHP(以下统称为 tp) 并没有这样做,而是在文档 架构 模块的 URL 访问 章节提了一下 tp 的 URL 重写。
[ Apache ]
httpd.conf 配置文件中加载了 mod_rewrite.so 模块
AllowOverride None 将 None 改为 All
把下面的内容保存为 .htaccess 文件放到应用入口文件的同级目录下
<IfModule mod_rewrite.c>
Options +FollowSymlinks -Multiviews
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ index.php/$1 [QSA,PT,L]
</IfModule>
[ IIS ]
如果你的服务器环境支持 ISAPI_Rewrite 的话,可以配置 httpd.ini 文件,添加下面的内容:
RewriteRule (.*)$ /index\.php\?s=$1 [I]
在 IIS 的高版本下面可以配置 web.Config,在中间添加 rewrite 节点:
<rewrite>
<rules>
<rule name="OrgPage" stopProcessing="true">
<match url="^(.*)$" />
<conditions logicalGrouping="MatchAll">
<add input="{HTTP_HOST}" pattern="^(.*)$" />
<add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
<add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
</conditions>
<action type="Rewrite" url="index.php/{R:1}" />
</rule>
</rules>
</rewrite>
[ Nginx ]
在 Nginx 低版本中,是不支持 PATHINFO 的,但是可以通过在 Nginx.conf 中配置转发规则实现:
location / { // …..省略部分代码
if (!-e $request_filename) {
rewrite ^(.*)$ /index.php?s=/$1 last;
}
}
其实内部是转发到了 ThinkPHP 提供的兼容 URL ,利用这种方式,可以解决其他不支持 PATHINFO 的 WEB 服务器环境。
对于主流的三个服务器,Apache 几乎不需要做什么配置,直接读取 public 下的 .htaccess
文件就好了。IIS 用得较少,暂不讨论。主要问题就是 Nginx 服务器了(其实理解了配置项的内容,也就没有问题了)。
解决方案
要解决配置文件改写的难题,就得理解配置文件内部配置项的目的和意义。类似于要读懂题目,才能解题的概念。
以下面的一个比较完整的配置文件做演示:
测试环境
CentOS Linux release 7.6.1810 (Core)
nginx version: nginx/1.14.0
PHP 7.3.3 (cli) (built: Apr 1 2019 18:16:04) ( NTS )
Nginx 配置详情
server {
listen 80;
server_name thinkphp.lo;
root /var/www;
index index.html index.htm index.php;
error_page 404 /404.html;
location = /404.html {
return 404 'Sorry, File not Found!';
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html; # windows用户替换这个目录
}
location / {
try_files $uri @rewrite;
}
location @rewrite {
set $static 0;
if ($uri ~ \.(css|js|jpg|jpeg|png|gif|ico|woff|eot|svg|css\.map|min\.map)$) {
set $static 1;
}
if ($static = 0) {
rewrite ^/(.*)$ /index.php?s=/$1;
}
}
location ~ /Uploads/.*\.php$ {
deny all;
}
location ~ \.php/ {
if ($request_uri ~ ^(.+\.php)(/.+?)($|\?)) { }
fastcgi_pass 127.0.0.1:9000;
include fastcgi_params;
fastcgi_param SCRIPT_NAME $1;
fastcgi_param PATH_INFO $2;
fastcgi_param SCRIPT_FILENAME $document_root$1;
}
location ~ \.php$ {
fastcgi_pass 127.0.0.1:9000;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
location ~ /\.ht {
deny all;
}
}
Nginx 配置详情 转自:老朱亲自写的,最完美ThinkPHP Nginx 配置文件 - ThinkPHP 框架
我们把内容拆分一下:
listen 80;
server_name thinkphp.lo;
root /var/www;
index index.html index.htm index.php;
这部分是 Nginx 下所有的虚拟主机都要求存在的。
摘抄一个 nginx.conf 内推荐的简单配置:
#server {
# listen 8000;
# listen somename:8080;
# server_name somename alias another.alias;
# location / {
# root html;
# index index.html index.htm;
# }
#}
一点差别在于根目录和索引文件配置项的位置,后者把他们放到 location / {}
中,其实没多大差别。后面摘抄的简单配置在不需要 PHP 参与(简单 html 静态页面)的情况下直接就可以用了。
error_page 404 /404.html;
location = /404.html {
return 404 'Sorry, File not Found!';
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html; # windows用户替换这个目录
}
这部分属于错误页面定义,对常见的 404、50x 错误,选择自定义错误页面,而不是展示服务器默认的页面。
location ~ /Uploads/.*\.php$ {
deny all;
}
...
location ~ /\.ht {
deny all;
}
这部分也很好理解(大部分规则都会应用到具体访问路径或文件上,所以一般都是 location 加上定义的配置规则,然后内部是匹配规则的请求处理),就是当访问 Uploads 下的 php 文件时,拒绝访问(防止上传了恶意脚本然后被执行),后面就是禁止访问 htaccess 文件(猜的)。
解析到了这里,剩下的都是核心部分了。核心部分主要分成两部分:URL 重写部分和 php 脚本解析部分。
URL 重写
location / {
try_files $uri @rewrite;
}
location @rewrite {
set $static 0;
if ($uri ~ \.(css|js|jpg|jpeg|png|gif|ico|woff|eot|svg|css\.map|min\.map)$) {
set $static 1;
}
if ($static = 0) {
rewrite ^/(.*)$ /index.php?s=/$1;
}
}
这部分前面的很像一种格式 try_files $uri $uri/ /index.php$uri;
。只不过因为内容较多,把重写部分放在了 {}
内部。尝试解读,就是如果 uri 部分包含 css|js|... 等后缀,即请求这些类型的文件时,不执行重写。或者反过来说,如果不是请求这些具体的资源文件时,就执行 URL 重写。
等等,我好想还见过另外一种写法的改写:
location / {
if (!-e $request_filename) {
rewrite ^(.*)$ /index.php?s=/$1 last;
#rewrite ^/(.*)$ /index.php/$1 last;
#break;
}
}
其实这两者效果一样的,只不过低版本的 nginx (具体低到什么版本我也不清楚)不支持 try_files 规则。
那么重写规则,到底重写了什么呢?这是关键。这就得说说 TP 框架的四种模式了(最新版本 TP 取消了普通模式,所以只剩下三种)
这些模式主要针对的是 TP 路由传递的方式来讲的,简单来讲,就是模块、控制器、方法名的传递方式。
普通模式
使用正常的 query_string 参数来传递路由信息。示例:http://域名/项目名/入口文件?m=模块名&a=方法名&键1=值1&键2=值2
。
PATHINFO 模式
使用 PATHINFO 虚拟路径来传递路由信息。示例:http://域名/项目名/入口文件/模块名/方法名/键1/值1/键2/值2
。
REWRITE 重写模式
在 PATHINFO 模式的基础上,去掉入口文件便于 SEO 优化。示例:http://域名/项目名/模块名/方法名/键1/值1/键2/值2
。
兼容模式
TP 框架自带的一种模式,主要兼容服务器不支持 PATHINFO、虚拟主机也不能自定义配置的情况。示例:http://域名/项目名/入口文件?s=模块名/方法名/键1/值1/键2/值2
。可以看出来,与 PATHINFO 模式的主要区别在于 PATHINFO 部分的传入方式。本来如果服务器支持 PATHINFO 解析,或者可以自定义虚拟主机配置 PATHINFO 参数是没有问题,但如果这两种情况都不能满足的话,又想使用形似 PATHINFO 模式传递路由信息和其他参数,就将 PATHINFO 部分通过参数 s 传入框架,框架会自动识别参数的。
除了普通模式和框架自带的兼容模式以外,要实现其他两种模式都需要借助服务器的额外配置。
就 PATHINFO 模式而言,关键点在于服务器是否支持 PATHINFO 模式,如果支持的话(php.ini 中 cgi.fix_pathinfo 项设为 1),服务器会自动解析出 URL 中的 PATHINFO 部分内容,并将它放到 $_SERVER['pathinfo']
参数中。官方文档里说关闭 PATHINFO 模式可以 当文件不存在时,阻止 Nginx 将请求发送到后端的 PHP-FPM 模块,从而避免恶意脚本注入的攻击
。所以对于可能没有开启 PATHINFO 的情况,又有通过 fastcgi_split_path_info
来分离 pathinfo 参数的配置方法。
具体分离方法如下(一般放在 php 脚本解析部分):
#设置PATH_INFO,注意fastcgi_split_path_info已经自动改写了fastcgi_script_name变量,
#后面不需要再改写SCRIPT_FILENAME,SCRIPT_NAME环境变量,所以必须在加载fastcgi.conf之前设置
fastcgi_split_path_info ^(.+\.php)(/.*)$;
fastcgi_param PATH_INFO $fastcgi_path_info;
而在这个基础上的 REWRITE 重写模式,就是将 URL 中省略的入口文件 index.php 重写到 URL 中。
rewrite ^/(.*)$ /index.php/$1 last;
但如果服务器不支持 PATHINFO 模式,而自己去改写虚拟主机配置又太麻烦,或者一直调试不好的话(我经历过这样的情况),可以采用重写方式使用兼容模式。如下:
rewrite ^(.*)$ /index.php?s=/$1 last;
php 脚本解析
location ~ \.php/ {
if ($request_uri ~ ^(.+\.php)(/.+?)($|\?)) { }
fastcgi_pass 127.0.0.1:9000;
include fastcgi_params;
fastcgi_param SCRIPT_NAME $1;
fastcgi_param PATH_INFO $2;
fastcgi_param SCRIPT_FILENAME $document_root$1;
}
location ~ \.php$ {
fastcgi_pass 127.0.0.1:9000;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
为什么会有两组针对 php 脚本解析的内容?其实这两组配置是不一样的,前者是针对有 PATHINFO 部分内容的 URL,解析内部的 PATHINFO;后者是针对访问内容只到入口文件没有 PATHINFO 的情况,这样直接解析就好。
有时我们也会定义错误日志文件和访问日志文件,一般放在 server 配置内部的末尾,如下:
error_log /var/log/nginx/ci.error.log;
access_log /var/log/nginx/ci.access.log;