使用 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/anything
是302
重定向到/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
指令。
这
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 采取了什么操作。