.htaccess mod_rewrite 没有捕获所有 RewriteRules

.htaccess mod_rewrite 没有捕获所有 RewriteRules

有一个 PHP 应用程序,其中有一个 PHP 路由器作为 index.php 中所有请求的入口点。我正在尝试编写一个 .htaccess 文件,将除 API 请求和设计资源之外的每个请求转发到 index.php。因此,我试图获得以下行为:

  1. example.com/api/v1/*应该服务api_v1.php
  2. example.com/any_path/resource.css=>resource.css如果存在则应该提供服务(允许多个扩展;.css只是一个例子)
  3. 适用index.php于任何不符合上述条件的情况

鉴于.htaccess从上到下、从特殊到一般条件进行评估,并且[L]如果有任何匹配项,该标志将中断执行,我设法得出以下结论.htaccess

RewriteEngine On

# Prevent 301 redirect with slash when folder exists and does not have slash appended
# This is not a security issue here since a PHP router is used and all the paths are redirected
DirectorySlash Off

#1. Rewrite for API url
RewriteRule ^api/v1/(.*)$ api_v1.php [L,NC]

#2. Rewrite to index.php except for for design/document/favicon/robots files that exists
RewriteCond %{REQUEST_URI} !.(css|js|png|jpg|jpeg|bmp|gif|ttf|eot|svg|woff|woff2|ico|webp|pdf)$
RewriteCond %{REQUEST_URI} !^(robots\.txt|favicon\.ico)$ [OR]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ index.php [L]

#3. Rewrite enything else
RewriteRule ^(.*)$ index.php [L]

使用上面的代码,似乎访问example.com/api/v1/不会执行api_v1.php。相反,它将继续并执行index.php

example.com/api/v1/仅当我删除第 8 行之后的所有条件时才会起作用。

我在这里做错了什么?

答案1

似乎访问 site.com/api/v1/ 不会执行 api_v1.php。相反,它会继续并执行 index.php

是的,因为第一条规则将请求重写为,而第二条规则api_v1.php最终将其重写为(因为不在第一条规则的排除扩展列表中index.phpphp状况,第二个条件也成功了,所以在重写引擎的第二遍传递过程中,第三个条件不会被处理(见下文)。在目录上下文(即.htaccessL标志不会停止所有处理,它只是停止当前通过重写引擎的过程。重写引擎会循环,直到 URL 保持不变为止。

您可以在 Apache 2.4 上解决这个问题END,通过在第一条规则上使用标志(而不是L)来停止重写引擎的所有处理。

例如:

RewriteRule ^api/v1/ api_v1.php [NC,END]

(我删除了正则表达式的尾随,(.*)$因为这里不需要它,并且会使正则表达式的效率略有降低。)

然而,对现有文件的请求.php被您后面的规则重写这一事实确实表明您的第二条和第三条规则似乎没有按预期工作......

#2. Rewrite to index.php except for for design/document/favicon/robots files that exists
RewriteCond %{REQUEST_URI} !.(css|js|png|jpg|jpeg|bmp|gif|ttf|eot|svg|woff|woff2|ico|webp|pdf)$
RewriteCond %{REQUEST_URI} !^(robots\.txt|favicon\.ico)$ [OR]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ index.php [L]

#3. Rewrite enything else
RewriteRule ^(.*)$ index.php [L]

如果你请求something.php那么第一个状况成功(.php因为不是包含在列表中)。第二个条件也成功(不是robots.txtfavicon.ico)。由于第二个条件明确与第三个条件进行或运算,因此不处理第三个条件。所有条件都成功,请求被重写为index.php(无论是否something.php存在)。

另一方面,如果你请求,resource.css那么第一个状况失败(因为它有.css扩展)。由于此条件是隐式 AND,因此不会处理其他条件,也不会触发规则。然后,第三条规则无条件重写resource.cssindex.php,无论它是否存在!

因此,这并不会检查所请求的资源(cssjpg等)是否“存在”,这似乎暗示了前面的评论。第三个条件(检查请求是否映射到文件)仅在前面的 OR 条件失败且第一个条件成功时才处理。换句话说,第三个条件仅在您提出请求时才处理/robots.txt- 我确信这不是您的意图。

换句话说,规则 #2 和 #3(尽管它们看起来很复杂)似乎重写了一切index.php

完整的解决方案

根据您的补充评论:

  • 对于文件类型的子集,提供资源 - 如果存在。如果资源不存在,则将请求发送到index.php

  • index.php对于其他文件类型,无论该资源是否存在,都请将请求发送至。

  • 一些请求(即/robots.txt/favicon.ico)不应发送至index.php

请尝试以下操作:

# Prevent 301 redirect with slash when folder exists and does not have slash appended
# This is not a security issue here since a PHP router is used and all the paths are redirected
DirectorySlash Off

# Since "DirectorySlash Off" is set, ensure that mod_auotindex directory listings are disabled
Options -Indexes

RewriteEngine On

#1. Rewrite for API url
RewriteRule ^api/v1/ api_v1.php [NC,END]

#2. Known URLs/files are served directly
RewriteRule ^(index\.php|robots\.txt|favicon\.ico)$ - [END]

#3. Certain file types (resources) are served directly if they exist
RewriteCond %{REQUEST_FILENAME} -f
RewriteRule \.(css|js|png|jpe?g|bmp|gif|ttf|eot|svg|woff|woff2|ico|webp|pdf)$ - [END]

#4. Rewrite everything else
RewriteRule ^ index.php [END]

对于需要直接提供的任何内容,规则都会提前完成,因此我们只需要index.php在最后一条规则中重写一次。

请注意,我已将其包含index.php在规则 #2 中。这是一种优化,尽管如果在最后一条规则上使用END标志(而不是),则并非绝对必要。L

或者,您可以选择重定向任何直接的请求index.php返回到根目录(以便搜索引擎规范化 URL)。这只是为了防止/index.php最终用户看到(或猜到)这些信息。

例如,在 API 的第一条规则后添加以下内容:

#1.5 Redirect direct requests to "/index.php" back to "/" (root)
RewriteCond %{ENV:REDIRECT_STATUS} ^$
RewriteRule ^index\.php$ / [R=301,L]

状况检查REDIRECT_STATUS环境变量是必要的,以防止重定向循环 - 避免重定向重写的 URL。(尽管,如果END在 last/rewrite 规则上使用标志,则这并不是严格要求的。)

答案2

尝试在每个规则前放置一个重写条件,然后打破规则。换句话说,每个条件都需要有自己的规则。

条件 -> 规则
条件 -> 规则
...

相关内容