HTTPS / HTTP mod_rewrite 规则,与后续 CMS index.php 重写的奇怪交互

HTTPS / HTTP mod_rewrite 规则,与后续 CMS index.php 重写的奇怪交互

使用 htaccess 中的以下重写,符合预期,此 HTTPS 请求是不是重写为:

https://example.com/system/anything

不是重写为

http://example.com/system/anything

但出乎意料的是,这个HTTPS请求被重写了:

https://example.com/preview/anything

重写为

http://example.com/index.php/preview/anything

为什么是这样?

其他一些事实/观察:

/system/是服务器上的实际路径。但/preview/不是实际路径——它是 CMS 中有意义的 URL 片段,例如,/index.php/preview/anything它是 CMS 获取 URL 请求的方式/preview/anything

其他非系统 URL 确实可以正确重写(从 HTTPS 到 HTTP),并且正确传递到 index.php。例如,

https://example.com/real

重写为

http://example.com/real

以下是完整的规则:

 <IfModule mod_rewrite.c>
 RewriteEngine On
 RewriteBase /

 # Force HTTPS for System URLs
 RewriteCond %{REQUEST_URI} ^/system(.*)$ [NC]
 RewriteCond %{HTTPS} !=on
 RewriteRule ^(.*)$ "https://example.com/$1" [R=301,L]

 # Force HTTP for Other URLs, but not: system or preview
 RewriteCond %{REQUEST_URI} !^/(system|preview)/(.*)$ [NC]
 RewriteCond %{HTTPS} =on
 RewriteRule ^(.*)$ "http://example.com/$1" [R=302,L]

 RewriteCond %{REQUEST_FILENAME} !-f
 RewriteCond %{REQUEST_FILENAME} !-d
 RewriteRule ^(.*)$ /index.php/$1 [L]
 </IfModule>

对于为何/preview/会受到如此奇怪的待遇,有什么见解吗?


添加:请注意,这/preview/anything302重定向到/index.php/preview/anything-- 而这在很大程度上看起来很奇怪/出乎意料。它不应该在最终规则中获得重定向,而应该只是重写。

答案1

这些重写规则是否在.htaccess文件中?在这种情况下旗帜[L]并没有发挥你想象的作用 — 它会停止当前规则集的处理,但随后 Apache 会使用适合重写 URI 的文件再次处理请求.htaccess,因此您的规则可能会再次执行。对于 Apache 配置文件中的规则(而不是部分内的规则),不会发生这种情况<Directory>— 在这种情况下,[L]标志会按预期处理。

对于您的示例,由第三条规则https://example.com/preview/anything在内部重写https://example.com/index.php/preview/anything;但是,为了处理该请求,Apache 必须.htaccess再次读取该文件 - 而这一次 URI 与您的第二条规则匹配,从而返回重定向302

Apache 2.4.x支持[END]停止此类重写循环的标志,与[L];早期 Apache 版本的解决方案更加复杂。

如果您想确保 Apache 只执行一次重写规则,您可以在所有其他规则之前添加以下规则:

RewriteCond %{ENV:REDIRECT_STATUS} !=""
RewriteRule ^ - [L]

第一次传递REDIRECT_STATUS将为空;第二次传递它将具有非空值(通常200),并且规则将匹配并真正停止进一步的重写。

如果这样的规则不合适(例如,在某些情况下您需要在第二次传递时处理重写的 URI),您可以在规则中设置一个真正最终的环境变量:

RewriteRule ^(.*)$ /index.php/$1 [L,E=FINISH:1]

并在所有其他规则之前添加以下规则:

RewriteCond %{ENV:REDIRECT_FINISH} !=""
RewriteRule ^ - [L]

请注意,在第二遍执行期间,Apache 会将REDIRECT_在第一遍执行期间定义的环境变量名称添加到前面,因此您需要设置FINISH,但测试REDIRECT_FINISH

或者,您可以尝试修改匹配条件,以便第二次传递时不会匹配第一次传递时修改的 URI(例如,插入(index\.php/)?到第二条规则中的正则表达式中)。

答案2

http这听起来像是最后一条规则在执行时引入了基于绝对的重定向,这可能是由于 Apache 或 mod_rewrite 中的一些错误或功能造成的。

您如何将其分解为两个规则并附加一个条件?

尝试这个:

RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{HTTPS} =off
RewriteRule ^(.*)$ http://example.com/index.php/$1 [L]

RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{HTTPS} =on
RewriteRule ^(.*)$ https://example.com/index.php/$1 [L]

答案3

http方案 (而不是)是否https让您感到困扰?如果是,您应该查看该RewriteBase指令。

来自mod_rewrite 文档

RewriteBase指令指定每个目录 (htaccess) 使用的 URL 前缀RewriteRule替代相对路径的指令。

当您在每个目录(htaccess)上下文中的替换中使用相对路径时,需要此指令(...)

答案4

我认为您不需要在 RewriteCond 语句末尾使用 '(.*)$',因为您没有在任何地方使用捕获的数据。您可以像这样简化这两个语句:

RewriteCond %{REQUEST_URI} ^/system [NC]

和 RewriteCond %{REQUEST_URI} !^/(system|preview)/ [NC]

我还建议打开 RewriteLog 并设置 RewriteLogLevel,以准确查看 apache 对每个请求执行的操作。对于 apache 2.2 或更低版本,它将是:

RewriteLog /var/log/apache2/rewrite.log
RewriteLogLevel 7

您应该逐步地看到究竟匹配了什么以及 apache 采取了什么操作。

相关内容