【发布时间】:2010-09-22 02:37:08
【问题描述】:
最近似乎有相当数量的mod_rewrite 线程在四处游荡,对它的某些方面如何工作感到有些困惑。因此,我整理了一些关于常见功能的注释,也许还有一些令人讨厌的细微差别。
您在使用mod_rewrite 时遇到了哪些其他功能/常见问题?
【问题讨论】:
标签: apache .htaccess mod-rewrite
最近似乎有相当数量的mod_rewrite 线程在四处游荡,对它的某些方面如何工作感到有些困惑。因此,我整理了一些关于常见功能的注释,也许还有一些令人讨厌的细微差别。
您在使用mod_rewrite 时遇到了哪些其他功能/常见问题?
【问题讨论】:
标签: apache .htaccess mod-rewrite
mod_rewrite 规则可以放在httpd.conf 文件中,也可以放在.htaccess 文件中。如果您有权访问 httpd.conf,则将规则放置在此处将提供性能优势(因为规则只处理一次,而不是每次调用 .htaccess 文件时)。
可以从httpd.conf 文件(包括<Virtual Host>)中启用日志记录:
# logs can't be enabled from .htaccess
# loglevel > 2 is really spammy!
RewriteLog /path/to/rewrite.log
RewriteLogLevel 2
将所有请求集中到一个点:
RewriteEngine on
# ignore existing files
RewriteCond %{REQUEST_FILENAME} !-f
# ignore existing directories
RewriteCond %{REQUEST_FILENAME} !-d
# map requests to index.php and append as a query string
RewriteRule ^(.*)$ index.php?query=$1
从 Apache 2.2.16 开始,您还可以使用FallbackResource。
处理 301/302 重定向:
RewriteEngine on
# 302 Temporary Redirect (302 is the default, but can be specified for clarity)
RewriteRule ^oldpage\.html$ /newpage.html [R=302]
# 301 Permanent Redirect
RewriteRule ^oldpage2\.html$ /newpage.html [R=301]
注意:外部重定向是隐含的 302 重定向:
# this rule:
RewriteRule ^somepage\.html$ http://google.com
# is equivalent to:
RewriteRule ^somepage\.html$ http://google.com [R]
# and:
RewriteRule ^somepage\.html$ http://google.com [R=302]
强制使用 SSL
RewriteEngine on
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://example.com/$1 [R,L]
常用标志:
[R] 或 [redirect] - 强制重定向(默认为 302 临时重定向)[R=301] 或 [redirect=301] - 强制执行 301 永久重定向[L] 或 [last] - 停止重写过程(请参阅下面的常见陷阱注释)[NC] 或 [nocase] - 指定匹配不区分大小写
使用长格式的标志通常更具可读性,并有助于以后阅读您的代码的其他人。
您可以用逗号分隔多个标志:
RewriteRule ^olddir(.*)$ /newdir$1 [L,NC]
将mod_alias 样式重定向与mod_rewrite 混合使用
# Bad
Redirect 302 /somepage.html http://example.com/otherpage.html
RewriteEngine on
RewriteRule ^(.*)$ index.php?query=$1
# Good (use mod_rewrite for both)
RewriteEngine on
# 302 redirect and stop processing
RewriteRule ^somepage.html$ /otherpage.html [R=302,L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
# handle other redirects
RewriteRule ^(.*)$ index.php?query=$1
注意:您可以将mod_alias 与mod_rewrite 混合使用,但它所涉及的工作不仅仅是处理上述基本重定向。
上下文影响语法
在.htaccess 文件中,RewriteRule 模式中不使用前导斜杠:
# given: GET /directory/file.html
# .htaccess
# result: /newdirectory/file.html
RewriteRule ^directory(.*)$ /newdirectory$1
# .htaccess
# result: no match!
RewriteRule ^/directory(.*)$ /newdirectory$1
# httpd.conf
# result: /newdirectory/file.html
RewriteRule ^/directory(.*)$ /newdirectory$1
# Putting a "?" after the slash will allow it to work in both contexts:
RewriteRule ^/?directory(.*)$ /newdirectory$1
[L] 不是最后一个! (有时)
[L] 标志停止处理任何进一步的重写规则通过规则集。但是,如果在该通道中修改了 URL,并且您位于 .htaccess 上下文或 <Directory> 部分,那么您修改后的请求将再次通过 URL 解析引擎传回。并且在下一次通过时,它可能会匹配不同的规则。如果您不理解这一点,通常看起来您的[L] 标志无效。
# processing does not stop here
RewriteRule ^dirA$ /dirB [L]
# /dirC will be the final result
RewriteRule ^dirB$ /dirC
我们的重写日志显示规则运行了两次,URL 更新了两次:
rewrite 'dirA' -> '/dirB'
internal redirect with /dirB [INTERNAL REDIRECT]
rewrite 'dirB' -> '/dirC'
如果您确实想停止所有进一步的规则处理(以及后续传递),最好的解决方法是使用 [END] 标志 (see Apache docs) 而不是 [L] 标志。但是,[END] 标志仅适用于 Apache v2.3.9+,因此如果您拥有 v2.2 或更低版本,则只能使用 [L] 标志。
对于早期版本,您必须依靠 RewriteCond 语句来防止在 URL 解析引擎的后续传递中匹配规则。
# Only process the following RewriteRule if on the first pass
RewriteCond %{ENV:REDIRECT_STATUS} ^$
RewriteRule ...
或者您必须确保您的 RewriteRule 位于不会导致您的请求被重新解析的上下文中(即httpd.conf)。
【讨论】:
[L] 标志表示规则在当前处理中是 last,这不会停止重写,因为它们是内部重定向,所以您的 dirB 适用于 @987654367 @ 在下一个 htaccess 处理中。单独RewriteRule ^(.*)$ index.php?query=$1 将是内部重定向的无限循环(实际上它在 10 次迭代后终止)。 -1 因为你建议 [L] 不是最后一个。这不是终止重写过程,而是这是最后一个。
RewriteCond %{HTTPS} off 是检查 HTTPS 连接的首选方式(在您的示例中,强制非 SSL 流量到 HTTPS)
【讨论】:
[L] 标志的每个关于.* 的问题提出这个建议。
200、!=200、^.、^$ 的一些修改。显然,该变量被设置为200 以进行重定向,但其他页面(错误和其他内容)也将其设置为某个值。现在这意味着您可以根据需要检查它是is empty、is not empty、is 200 还是is not 200。
与 RewriteBase 的交易:
您几乎总是需要设置 RewriteBase。如果你不这样做,apache 会猜测你的 base 是你目录的物理磁盘路径。所以从这个开始:
RewriteBase /
【讨论】:
RewriteBase .,或者说它应该保持URL不变,只是改变你指定的内容?
RewriteRule 指令中使用相对路径替换,您只需要设置RewriteBase。最好避免使用相对路径。
RewriteBase,因为几乎所有开发人员都误解了它的作用。正如@w3d 所说,只有当您想要保存字符并且想要将相同的基础应用于一个文件中的所有 RewriteRules 时,您才需要它。如果您避免使用代码,其他人可能会更清楚。
其他陷阱:
1- 有时禁用 MultiViews 是个好主意
Options -MultiViews
我不太了解 MultiViews 的所有功能,但我知道它在激活时会扰乱我的 mod_rewrite 规则,因为它的属性之一是尝试“猜测”它认为我的文件的扩展名我在找。
我会解释: 假设你的 web 目录中有 2 个 php 文件,file1.php 和 file2.php,你将这些条件和规则添加到你的 .htaccess 中:
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ file1.php/$1
您假设所有与文件或目录不匹配的 url 都将被 file1.php 抓取。惊喜!此规则不适用于 url http://myhost/file2/somepath。相反,您被带入了 file2.php。
发生的事情是 MultiViews 自动猜测您真正想要的 url 是 http://myhost/file2.php/somepath 并很高兴带您到那里。
现在,您对刚刚发生的事情一无所知,此时您正在质疑您认为自己了解的关于 mod_rewrite 的一切。然后,您开始使用规则来尝试理解这种新情况背后的逻辑,但是您测试的越多,它的意义就越小。
好的,简而言之,如果您希望 mod_rewrite 以接近逻辑的方式工作,关闭 MultiViews 是朝着正确方向迈出的一步。
2- 启用 FollowSymlinks
Options +FollowSymLinks
那个,具体细节我也不是很清楚,但是看过很多次了,就照着做吧。
【讨论】:
+FollowSymLinks 在文档中被提及是mod_rewrite 必须工作的必要条件,出于模糊的安全原因。
公式可以用下面的例子来完成:
RewriteCond %{REQUEST_URI} ^/(server0|server1).*$ [NC]
# %1 is the string that was found above
# %1<>%{HTTP_COOKIE} concatenates first macht with mod_rewrite variable -> "test0<>foo=bar;"
#RewriteCond search for a (.*) in the second part -> \1 is a reference to (.*)
# <> is used as an string separator/indicator, can be replaced by any other character
RewriteCond %1<>%{HTTP_COOKIE} !^(.*)<>.*stickysession=\1.*$ [NC]
RewriteRule ^(.*)$ https://notmatch.domain.com/ [R=301,L]
动态负载平衡:
如果您使用 mod_proxy 来平衡您的系统,则可以添加一个动态范围的工作服务器。
RewriteCond %{HTTP_COOKIE} ^.*stickysession=route\.server([0-9]{1,2}).*$ [NC]
RewriteRule (.*) https://worker%1.internal.com/$1 [P,L]
【讨论】:
为了更好地理解 [L] 标志。 [L] 标志 是 最后,您只需要了解什么会导致您的请求再次通过 URL 解析引擎进行路由。来自文档 (http://httpd.apache.org/docs/2.2/rewrite/flags.html#flag_l)(强调我的):
[L] 标志导致 mod_rewrite 停止处理规则集。在 大多数情况下,这意味着如果规则匹配,则没有其他规则 将被处理。这对应于 Perl 中的最后一个命令,或者 C 中的 break 命令。使用此标志来指示当前 规则应立即应用,无需考虑进一步的规则。
如果您在 .htaccess 文件或
<Directory>部分中使用 RewriteRule,了解以下内容很重要 如何处理规则。这个的简化形式是,一旦 规则已处理完毕,将重写的请求返回 URL 解析引擎用它来做它可能做的事情。它可能是 在处理重写请求时,.htaccess 文件或<Directory>部分可能会再次遇到,因此可以运行规则集 再次从头开始。最常见的情况是,如果其中一个 规则导致重定向 - 无论是内部的还是外部的 - 导致 请求流程重新开始。
因此 [L] 标志 停止处理 通过 规则的任何进一步重写规则放。但是,如果您的带有 [L] 标记的规则修改了请求,并且您位于 .htaccess 上下文或 <Directory> 部分,那么您修改后的请求将再次通过 URL 解析引擎传回。并且在下一次通过时,它可能会匹配不同的规则。如果您不明白发生了什么,看起来您的第一个带有 [L] 标志的重写规则无效。
如果您真的想停止所有进一步的规则处理(以及随后的重新解析),最好的解决方法是使用 [END] 标志 (http://httpd.apache.org/docs/current/rewrite/flags.html#flag_end) 而不是 [L] 标志。但是,[END] 标志仅适用于 Apache v2.3.9+,因此如果您拥有 v2.2 或更低版本,则只能使用 [L] 标志。在这种情况下,您必须依靠 RewriteCond 语句来防止在 URL 解析引擎的后续传递中匹配规则。或者您必须确保您的 RewriteRule 位于不会导致您的请求被重新解析的上下文(即 httpd.conf)中。
【讨论】:
另一个很棒的功能是 rewrite-map-expansions。如果您有大量的主机/重写要处理,它们特别有用:
它们就像键值替换:
RewriteMap examplemap txt:/path/to/file/map.txt
然后您可以在规则中使用如下映射:
RewriteRule ^/ex/(.*) ${examplemap:$1}
可以在此处找到有关此主题的更多信息:
http://httpd.apache.org/docs/2.0/mod/mod_rewrite.html#mapfunc
【讨论】:
.htaccess 的重写,请忽略此功能。在这种情况下它不起作用。
mod_rewrite 可以在不更改 URL 的情况下修改请求处理的各个方面,例如设置环境变量、设置 cookie 等。这非常有用。
有条件地设置一个环境变量:
RewriteCond %{HTTP_COOKIE} myCookie=(a|b) [NC]
RewriteRule .* - [E=MY_ENV_VAR:%b]
返回 503 响应:
RewriteRule 的 [R] 标志可以采用非 3xx 值并返回非重定向响应,例如对于托管停机时间/维护:
RewriteRule .* - [R=503,L]
将返回 503 响应(本身不是 重定向)。
此外,mod_rewrite 可以充当 mod_proxy 的超级强大接口,因此您可以这样做而不是编写 ProxyPass 指令:
RewriteRule ^/(.*)$ balancer://cluster%{REQUEST_URI} [P,QSA,L]
意见:
使用RewriteRules 和RewriteConds 根据请求的几乎任何可能的方面将请求路由到不同的应用程序或负载均衡器是非常强大的。控制到达后端的请求,并能够在返回的途中修改响应,这使得 mod_rewrite 成为集中所有与路由相关的配置的理想场所。
花时间学习它,非常值得! :)
【讨论】: