
本教程详细讲解如何在Nginx中实现URI重写,以剥离特定子目录(如/shop)并将其后续路径作为参数传递给后端PHP脚本(如main.php)。通过结合try_files和rewrite指令,本教程提供了一种高效且结构清晰的解决方案,旨在帮助用户在Nginx环境中构建类似Apache mod_rewrite的灵活路由机制,避免常见的配置陷阱。
1. 理解URI重写需求
在基于php-fpm的应用中,常见的一种路由模式是将所有请求都导向一个前端控制器(如index.php或main.php),然后由该控制器根据uri路径来决定加载哪个模块或页面。当应用部署在nginx的某个子目录(例如/shop)下时,我们可能希望将形如 example.com/shop/product/123 的请求内部转换为 example.com/shop/main.php?route=/product/123。这要求nginx能够:
- 识别以 /shop 开头的请求。
- 从URI中剥离 /shop 部分。
- 将剥离后的路径作为 route 参数传递给 main.php。
- 优先处理静态文件,如果请求的URI对应文件或目录存在,则直接提供服务。
2. 常见误区与Nginx指令解析
在尝试实现上述需求时,一些常见的错误配置方法及其原因如下:
-
错误地在 try_files 中使用 $1 变量:
location ^~ /shop/product { try_files $uri $uri/ @rewrite; } location @rewrite { try_files $uri $uri/ /shop/main.php?route=$1 ; # 这里的 $1 是无效的 }登录后复制$1 等捕获组变量仅在 rewrite 指令中,通过正则表达式匹配后才能被赋值。try_files 指令的主要作用是按顺序检查文件或目录是否存在,并提供回退机制,它不具备正则表达式匹配和捕获组赋值的能力。因此,在 try_files 中直接使用 $1 会导致变量未定义,通常表现为404错误。
-
直接使用 $uri 作为参数:
location /shop { try_files $uri $uri/ /shop/main.php?route=$uri; }登录后复制这种方式会将完整的URI(例如 /shop/product/123)作为 route 参数传递,而不是我们期望的 /product/123。这不符合剥离子目录的需求。
要正确实现URI重写,我们需要利用Nginx的 rewrite 指令,它专门用于基于正则表达式进行URI转换。
3. Nginx URI重写解决方案
以下是实现上述路由需求的Nginx配置示例:
server {
listen 80;
server_name example.com;
root /var/www/html; # 你的项目根目录,main.php 位于 /var/www/html/shop/main.php
index index.php index.html;
# PHP-FPM 配置(示例,请根据实际情况调整)
location ~ /.php$ {
fastcgi_pass unix:/run/php/php7.4-fpm.sock; # 或 fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
# 核心URI重写逻辑
location /shop/ {
# 1. 尝试直接提供静态文件或目录
# 如果 /var/www/html/shop/product/123 存在文件或目录,则直接服务
try_files $uri $uri/ @rewrite_shop;
}
# 2. 定义一个命名location来处理重写逻辑
location @rewrite_shop {
# 使用 rewrite 指令进行URI转换
# ^/shop(/.*) : 匹配以 /shop 开头,并捕获 /shop 之后的所有内容到 $1
# /shop/main.php?route=$1 : 重写目标,将捕获的 $1 作为 route 参数
# last : 停止当前 location 的处理,并用新URI重新进行 location 匹配
rewrite ^/shop(/.*) /shop/main.php?route=$1 last;
}
# 阻止访问 .htaccess 文件(如果存在,虽然Nginx不使用)
location ~ //.ht {
deny all;
}
}
4. 配置详解
-
location /shop/ { … }:
- 这个 location 块用于匹配所有以 /shop/ 开头的URI请求。
- try_files $uri $uri/ @rewrite_shop;:这是Nginx处理请求的推荐方式。它会按顺序执行以下操作:
-
location @rewrite_shop { … }:
- 这是一个命名 location 块,它不能直接匹配外部请求,只能通过 try_files 或其他内部重定向指令引用。
- rewrite ^/shop(/.*) /shop/main.php?route=$1 last;:这是实现核心重写逻辑的关键。
- rewrite:Nginx的重写指令。
- ^/shop(/.*):一个正则表达式。
- ^:匹配URI的开始。
- /shop:字面匹配 /shop。
- (/.*):捕获组。.* 匹配除换行符外的任何字符零次或多次。括号 () 将匹配到的内容捕获到 $1 变量中。这意味着,如果URI是 /shop/product/123,那么 $1 将是 /product/123。
- /shop/main.php?route=$1:重写后的目标URI。$1 会被正则表达式捕获到的内容替换。
- last:这是一个标志位。它告诉Nginx停止处理当前的 rewrite 指令集,并用新生成的URI(/shop/main.php?route=/product/123)重新开始 location 匹配过程。这意味着新的URI会再次被Nginx的 location 块进行匹配,最终可能会被 location ~ /.php$ 块捕获并传递给PHP-FPM处理。
5. 与Apache .htaccess 的对比
Apache的 .htaccess 文件中的 RewriteRule (.*) main.php?route=$1 规则通常在 RewriteBase /shop 的上下文中使用,或者通过 RewriteRule ^shop/(.*) shop/main.php?route=$1 实现类似效果。
Nginx的 rewrite 指令与Apache的 mod_rewrite 具有相似的功能,但工作方式略有不同。Nginx的配置是集中式的,通常在 server 块中定义,而Apache的 .htaccess 允许分布式配置。Nginx的 last 标志在功能上类似于Apache的 [L] (Last) 标志,都表示停止当前规则集的处理并重新开始URI匹配。
6. 注意事项与最佳实践
- 性能优化: 尽可能使用 try_files 来直接服务静态文件,只有在文件不存在时才进行重写,这样可以减少PHP-FPM的负载。
- 正则表达式准确性: 确保 rewrite 指令中的正则表达式准确匹配你想要转换的URI部分,并正确捕获所需参数。
-
last vs break vs redirect:
- last:停止当前 location 的处理,用新URI重新进行 location 匹配。适用于内部重写,通常是期望将请求传递给另一个 location 块(如PHP处理器)。
- break:停止当前 location 的 rewrite 指令处理,但继续在该 location 块内处理其他指令。不推荐用于复杂的路由场景。
- redirect:返回一个302临时重定向响应给客户端,浏览器会用新URI发起新的请求。适用于外部可见的URL变更。
- permanent:返回一个301永久重定向响应。
在本例中,last 是最合适的选择,因为它实现了内部重写,对客户端透明,并允许Nginx继续处理重写后的URI。
- PHP-FPM配置: 确保你的 location ~ /.php$ 块配置正确,能够将重写后的PHP脚本(如 /shop/main.php)传递给PHP-FPM处理。fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; 是关键,它确保PHP-FPM知道要执行哪个脚本文件。
7. 总结
通过本教程,我们学习了如何在Nginx中利用 location、try_files 和 rewrite 指令,高效且准确地实现URI重写,以剥离子目录并传递动态路由参数。这种方法不仅解决了特定场景下的路由需求,也展示了Nginx在处理复杂URI逻辑方面的强大能力和灵活性。理解这些核心指令及其配合使用方式,对于构建高性能、可维护的Nginx应用至关重要。
以上就是Nginx URI重写教程:剥离子目录实现灵活路由的详细内容,更多请关注php中文网其它相关文章!


