Mod_rewrite 的隐藏特性

最近似乎有相当数量的 mod_rewrite线程漂浮在周围,对它的某些方面是如何工作的有点困惑。因此,我编写了一些关于常见功能的说明,也许还有一些恼人的细微差别。

使用 mod_rewrite还遇到过哪些其他特性/常见问题?

45271 次浏览

在哪里放置 mod _ rewrite 规则

mod_rewrite规则可以放置在 httpd.conf文件中,也可以放置在 .htaccess文件中。如果您可以访问 httpd.conf,在这里放置规则将提供性能优势(因为规则处理一次,而不是每次调用 .htaccess文件)。

记录 mod _ rewrite 请求

可以从 httpd.conf文件(包括 <Virtual Host>)中启用日志记录:

# logs can't be enabled from .htaccess
# loglevel > 2 is really spammy!
RewriteLog /path/to/rewrite.log
RewriteLogLevel 2

常用案例

  1. 将所有请求集中到一个点:

    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

  2. 处理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]
    
  3. Forcing SSL

    RewriteEngine on
    RewriteCond %{HTTPS} off
    RewriteRule ^(.*)$ https://example.com/$1 [R,L]
    
  4. Common flags:

    • [R] or [redirect] - force a redirect (defaults to a 302 temporary redirect)
    • [R=301] or [redirect=301] - force a 301 permanent redirect
    • [L] or [last] - stop rewriting process (see note below in common pitfalls)
    • [NC] or [nocase] - specify that matching should be case insensitive


    Using the long-form of flags is often more readable and will help others who come to read your code later.

    You can separate multiple flags with a comma:

    RewriteRule ^olddir(.*)$ /newdir$1 [L,NC]
    

Common pitfalls

  1. Mixing mod_alias style redirects with 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_aliasmod_rewrite混合使用,但是它涉及的工作不仅仅是像上面那样处理基本的重定向。

  2. 上下文影响语法

    .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
    
  3. [L] is not last! (sometimes)

    The [L] flag stops processing any further rewrite rules for that pass through the rule set. However, if the URL was modified in that pass and you're in the .htaccess context or the <Directory> section, then your modified request is going to be passed back through the URL parsing engine again. And on the next pass, it may match a different rule this time. If you don't understand this, it often looks like your [L] flag had no effect.

    # 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]标志(查看 Apache 文档)而不是 [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 ...
    

    Or you must ensure that your RewriteRule's are in a context (i.e. httpd.conf) that will not cause your request to be re-parsed.

其他缺陷:

1-有时禁用多视图是个好主意

Options -MultiViews

我不是很熟悉 MultiView 的所有功能,但是我知道它在激活时会扰乱我的 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 抓取。惊喜吧!这条规则没有在网址 http://myhost/file2/somepath中得到遵守。取而代之的是取自 file2.php 内部。

事情是这样的: MultiView 自动猜到您实际需要的 URL 是 http://myhost/file2.php/somepath,并很高兴地将您带到了那里。

现在,你根本不知道刚才发生了什么,你正在质疑你认为你知道的关于 mod _ rewrite 的一切。然后,您开始玩弄规则,试图理解这个新情况背后的逻辑,但是您测试的越多,它就越没有意义。

好的,简而言之,如果你想让 mod _ rewrite 以一种近似逻辑的方式工作,关闭 MultiView 是朝着正确方向迈出的一步。

启用 FollowSymlinks

Options +FollowSymLinks

那个,我不太清楚具体是什么,但是我已经看过很多次了,所以就照做吧。

另一个很棒的特性是 rewrite-map-expsions。如果你有大量的主机/重写需要处理,它们尤其有用:

它们就像一个键值替代品:

RewriteMap examplemap txt:/path/to/file/map.txt

Then you can use a mapping in your rules like:

RewriteRule ^/ex/(.*) ${examplemap:$1}

你可在此浏览更多有关这个课题的资料:

Http://httpd.apache.org/docs/2.0/mod/mod_rewrite.html#mapfunc

与 RewriteBase 的交易:

您几乎总是需要设置 RewriteBase。如果不这样做,Apache 会猜测您的基底是您目录的物理磁盘路径。从这个开始:

RewriteBase /

Equation can be done with following example:

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 _ agent 来平衡系统,则可以添加辅助服务器的动态范围。

RewriteCond %{HTTP_COOKIE} ^.*stickysession=route\.server([0-9]{1,2}).*$ [NC]
RewriteRule (.*) https://worker%1.internal.com/$1 [P,L]

如果需要“阻止”. htaccess 中发生的内部重定向/重写,请查看

RewriteCond %{ENV:REDIRECT_STATUS} ^$

条件,作为 discussed here

Mod _ rewrite 可以在不改变 URL 的情况下修改请求处理的各个方面,例如设置环境变量、设置 cookie 等。这真是太有用了。

有条件地设定环境变量:

RewriteCond %{HTTP_COOKIE} myCookie=(a|b) [NC]
RewriteRule .* - [E=MY_ENV_VAR:%b]

Return a 503 response: RewriteRule[R]标志可以采用非3xx 值并返回非重定向响应,例如,对于管理停机/维护:

RewriteRule .* - [R=503,L]

将返回一个503响应(本身不是 再次询问)。

另外,mod _ rewrite 可以作为 mod _ agent 的超级强大接口,因此您可以这样做,而不是编写 ProxyPass指令:

RewriteRule ^/(.*)$ balancer://cluster%{REQUEST_URI} [P,QSA,L]

Opinion: 使用 RewriteRuleRewriteCond将请求路由到不同的应用程序或基于几乎任何可以想象到的请求方面的负载平衡器是非常强大的。在请求到达后端的过程中控制请求,并能够在请求返回的过程中修改响应,这使得 mod _ rewrite 成为集中所有与路由相关的配置的理想位置。

花点时间去学习吧,这是非常值得的! :)

A better understanding of the [L] flag is in order. The [L] flag last, you just have to understand what will cause your request to be routed through the URL parsing engine again. From the docs (http://httpd.apache.org/docs/2.2/rewrite/flags.html#flag_l) (emphasis mine):

标志使 mod _ rewrite 停止处理规则集 在大多数上下文中,这意味着如果规则匹配,则没有进一步的规则 将被处理。这对应于佩尔的最后一个命令,或者 中的 break 命令。使用此标志指示当前 规则应立即适用,而无须考虑进一步的规则。

If you are using RewriteRule in either .htaccess files or in <Directory> sections, it is important to have some understanding of 规则是如何被处理的。这个的简化形式是一次 规则已经处理,重写的请求被返回到 URL 解析引擎来做它可能做的事情 as the rewritten request is handled, the .htaccess file or <Directory> 部分,因此可以运行规则集 again from the start. Most commonly this will happen if one of the 规则导致重定向(内部或外部) ,从而导致 请求重新开始的过程。

因此,[ L ]标志 [俄语]通过规则集停止处理 那个传球的任何进一步重写规则。但是,如果标记为[ L ]的规则修改了请求,并且您在。Htaccess 上下文或 <Directory>部分,然后修改后的请求将再次通过 URL 解析引擎传递回来。在下一次传球时,它可能会符合一个不同的规则。如果您不理解发生了什么,那么看起来您的第一个带有[ L ]标志的重写规则没有任何效果。

The best way around this is to use the [END] flag (http://httpd.apache.org/docs/current/rewrite/flags.html#flag_end) instead of the [L] flag, if you truly want to stop all further processing of rules (and subsequent reparsing). However, the [END] flag is only available for Apache v2.3.9+, so if you have v2.2 or lower, you're stuck with just the [L] flag. In this case, you must rely on RewriteCond statements to prevent matching of rules on subsequent passes of the URL parsing engine. Or you must ensure that your RewriteRule's are in a context (i.e. httpd.conf) that will not cause your request to be re-parsed.