apache mod_rewrite 的隐藏功能

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/286004/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-13 16:57:11  来源:igfitidea点击:

Hidden features of mod_rewrite

apache.htaccessmod-rewrite

提问by Owen

There seem to be a decent number of mod_rewritethreads floating around lately with a bit of confusion over how certain aspects of it work. As a result I've compiled a few notes on common functionality, and perhaps a few annoying nuances.

mod_rewrite最近似乎有相当多的线程在流动,但对其某些方面的工作方式有些困惑。因此,我编写了一些关于常见功能的注释,也许还有一些令人讨厌的细微差别。

What other features / common issues have you run across using mod_rewrite?

您在使用中遇到过哪些其他功能/常见问题mod_rewrite

回答by Owen

Where to place mod_rewrite rules

在哪里放置 mod_rewrite 规则

mod_rewriterules may be placed within the httpd.conffile, or within the .htaccessfile. if you have access to httpd.conf, placing rules here will offer a performance benefit (as the rules are processed once, as opposed to each time the .htaccessfile is called).

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

Logging mod_rewrite requests

记录 mod_rewrite 请求

Logging may be enabled from within the httpd.conffile (including <Virtual Host>):

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

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

Common use cases

常见用例

  1. To funnel all requests to a single point:

    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= 
    

    Since Apache 2.2.16 you can also use FallbackResource.

  2. Handling 301/302 redirects:

    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] 
    

    Note: external redirects are implicitly 302 redirects:

    # 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/ [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 [L,NC]
    
  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= 
    

    从 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. 强制 SSL

    RewriteEngine on
    RewriteCond %{HTTPS} off
    RewriteRule ^(.*)$ https://example.com/ [R,L]
    
  4. 常用标志:

    • [R][redirect]- 强制重定向(默认为 302 临时重定向)
    • [R=301][redirect=301]- 强制 301 永久重定向
    • [L][last]- 停止重写过程(请参阅下面常见陷阱的注释)
    • [NC][nocase]- 指定匹配不区分大小写


    使用长格式的标志通常更具可读性,并有助于以后阅读您的代码的其他人。

    您可以用逗号分隔多个标志:

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

Common pitfalls

常见的陷阱

  1. Mixing mod_aliasstyle redirects with mod_rewrite

    # Bad
    Redirect 302 /somepage.html http://example.com/otherpage.html
    RewriteEngine on
    RewriteRule ^(.*)$ index.php?query=
    
    # 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=                 
    

    Note: you can mix mod_aliaswith mod_rewrite, but it involves more work than just handling basic redirects as above.

  2. Context affects syntax

    Within .htaccessfiles, a leading slash is not used in the RewriteRule pattern:

    # given: GET /directory/file.html
    
    # .htaccess
    # result: /newdirectory/file.html
    RewriteRule ^directory(.*)$ /newdirectory
    
    # .htaccess
    # result: no match!
    RewriteRule ^/directory(.*)$ /newdirectory
    
    # httpd.conf
    # result: /newdirectory/file.html
    RewriteRule ^/directory(.*)$ /newdirectory
    
    # Putting a "?" after the slash will allow it to work in both contexts:
    RewriteRule ^/?directory(.*)$ /newdirectory
    
  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 .htaccesscontext 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     
    

    Our rewrite log shows that the rules are run twice and the URL is updated twice:

    rewrite 'dirA' -> '/dirB'
    internal redirect with /dirB [INTERNAL REDIRECT]
    rewrite 'dirB' -> '/dirC'
    

    The best way around this is to use the [END]flag (see Apache docs) instead of the [L]flag, if you truly want to stop all further processing of rules (and subsequent passes). 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.

    For earlier versions, you must rely on RewriteCondstatements to prevent matching of rules on subsequent passes of the URL parsing engine.

    # 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. 混合mod_alias样式重定向mod_rewrite

    # Bad
    Redirect 302 /somepage.html http://example.com/otherpage.html
    RewriteEngine on
    RewriteRule ^(.*)$ index.php?query=
    
    # 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=                 
    

    注意:您可以mod_alias与混合使用mod_rewrite,但它涉及的工作不仅仅是处理上述基本重定向。

  2. 上下文影响语法

    .htaccess文件中,在 RewriteRule 模式中不使用前导斜杠:

    # given: GET /directory/file.html
    
    # .htaccess
    # result: /newdirectory/file.html
    RewriteRule ^directory(.*)$ /newdirectory
    
    # .htaccess
    # result: no match!
    RewriteRule ^/directory(.*)$ /newdirectory
    
    # httpd.conf
    # result: /newdirectory/file.html
    RewriteRule ^/directory(.*)$ /newdirectory
    
    # Putting a "?" after the slash will allow it to work in both contexts:
    RewriteRule ^/?directory(.*)$ /newdirectory
    
  3. [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]标志(请参阅 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 ...
    

    或者您必须确保您的 RewriteRule 位于httpd.conf不会导致您的请求被重新解析的上下文中(即)。

回答by mromaine

if you need to 'block' internal redirects / rewrites from happening in the .htaccess, take a look at the

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

RewriteCond %{ENV:REDIRECT_STATUS} ^$

condition, as discussed here.

条件,正如这里讨论的

回答by Sean McMillan

The deal with RewriteBase:

与 RewriteBase 的交易:

You almost always need to set RewriteBase. If you don't, apache guesses that your base is the physical disk path to your directory. So start with this:

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

RewriteBase /

回答by Michael Ekoka

Other Pitfalls:

其他陷阱:

1- Sometimes it's a good idea to disable MultiViews

1- 有时禁用 MultiViews 是个好主意

Options -MultiViews

I'm not well verse on all of MultiViews capabilities, but I know that it messes up my mod_rewrite rules when active, because one of its properties is to try and 'guess' an extension to a file that it thinks I'm looking for.

我不太了解 MultiViews 的所有功能,但我知道它在活动时会弄乱我的 mod_rewrite 规则,因为它的一个特性是尝试“猜测”它认为我正在寻找的文件的扩展名.

I'll explain: Suppose you have 2 php files in your web dir, file1.php and file2.php and you add these conditions and rule to your .htaccess :

我将解释:假设您的 web 目录中有 2 个 php 文件,file1.php 和 file2.php,并且您将这些条件和规则添加到您的 .htaccess 中:

RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ file1.php/ 

You assume that all urls that do not match a file or a directory will be grabbed by file1.php. Surprise! This rule is not being honored for the url http://myhost/file2/somepath. Instead you're taken inside file2.php.

您假设所有与文件或目录不匹配的 url 都将被 file1.php 抓取。惊喜!url http://myhost/file2/somepath不遵守此规则。相反,您被带到file2.php 中。

What's going on is that MultiViews automagically guessed that the url that you actually wanted was http://myhost/file2.php/somepathand gladly took you there.

发生的事情是 MultiViews 自动猜测您实际想要的 url 是http://myhost/file2.php/somepath并很高兴地将您带到那里。

Now, you have no clue what just happened and you're at that point questioning everything that you thought you knew about mod_rewrite. You then start playing around with rules to try to make sense of the logic behind this new situation, but the more you're testing the less sense it makes.

现在,您不知道刚刚发生了什么,并且您正在质疑您认为自己了解的有关 mod_rewrite 的所有内容。然后你开始玩弄规则,试图理解这种新情况背后的逻辑,但是你测试的越多,它就越没有意义。

Ok, In short if you want mod_rewrite to work in a way that approximates logic, turning off MultiViews is a step in the right direction.

好吧,简而言之,如果您希望 mod_rewrite 以接近逻辑的方式工作,那么关闭 MultiViews 是朝着正确方向迈出的一步。

2- enable FollowSymlinks

2- 启用 FollowSymlinks

Options +FollowSymLinks 

That one, I don't really know the details of, but I've seen it mentioned many times, so just do it.

那个,我不是很清楚,但我看到它提到很多次了,所以就去做吧。

回答by DrDol

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 ->  is a reference to (.*)
# <> is used as an string separator/indicator, can be replaced by any other character
RewriteCond %1<>%{HTTP_COOKIE} !^(.*)<>.*stickysession=.*$ [NC]
RewriteRule ^(.*)$ https://notmatch.domain.com/ [R=301,L]

Dynamic Load Balancing:

动态负载均衡:

If you use the mod_proxy to balance your system, it's possible to add a dynamic range of worker server.

如果您使用 mod_proxy 来平衡您的系统,则可以添加一个动态范围的工作服务器。

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

回答by JaredC

A better understanding of the [L] flag is in order. The [L] flag islast, 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):

更好地理解 [L] 标志是有必要的。[L] 标志最后一个,您只需要了解什么会导致您的请求再次通过 URL 解析引擎路由。从文档(http://httpd.apache.org/docs/2.2/rewrite/flags.html#flag_l)(强调我的):

The [L] flag causes mod_rewrite to stop processing the rule set. In most contexts, this means that if the rule matches, no further rules will be processed. This corresponds to the last command in Perl, or the break command in C. Use this flag to indicate that the current rule should be applied immediately without considering further rules.

If you are using RewriteRule in either .htaccess files or in <Directory>sections, it is important to have some understanding of how the rules are processed. The simplified form of this is that once the rules have been processed, the rewritten request is handed backto the URL parsing engine to do what it may with it. It is possible that as the rewritten request is handled, the .htaccess file or <Directory>section may be encountered again, and thus the ruleset may be run again from the start. Most commonly this will happen if one of the rules causes a redirect - either internal or external - causing the request process to start over.

[L] 标志导致 mod_rewrite 停止处理规则集。在大多数情况下,这意味着如果规则匹配,则不会处理其他规则。这对应于 Perl 中的最后一个命令,或 C 中的 break 命令。使用此标志来指示应立即应用当前规则而不考虑其他规则。

如果您在 .htaccess 文件或<Directory>部分中使用 RewriteRule,那么了解规则的处理方式很重要。其简化形式是,一旦处理了规则,重写的请求就会被传递回URL 解析引擎,以便对其进行处理。有可能在处理重写的请求时,<Directory>可能会再次遇到.htaccess 文件或部分,因此可能会从头开始再次运行规则集。最常见的是,如果规则之一导致重定向(内部或外部)导致请求过程重新开始,则会发生这种情况。

So the [L] flag doesstop processing any further rewrite rules for that passthrough the rule set. However, if your rule marked with [L] modified the request, and you're in the .htaccess context or the <Directory>section, then your modifed 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 what happened, it looks like your first rewrite rule with the [L] flag had no effect.

因此,[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.

如果您真的想停止,最好的方法是使用 [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)中。

回答by B.E.

Another great feature are rewrite-map-expansions. They're especially useful if you have a massive amout of hosts / rewrites to handle:

另一个很棒的功能是重写映射扩展。如果您要处理大量主机/重写,它们将特别有用:

They are like a key-value-replacement:

它们就像一个键值替换:

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

Then you can use a mapping in your rules like:

然后,您可以在规则中使用映射,例如:

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

More information on this topic can be found here:

可以在此处找到有关此主题的更多信息:

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

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

回答by cweekly

mod_rewrite can modify aspects of request handling without altering the URL, e.g. setting environment variables, setting cookies, etc. This is incredibly useful.

mod_rewrite 可以在不改变 URL 的情况下修改请求处理的各个方面,例如设置环境变量、设置 cookie 等。这非常有用。

Conditionally set an environment variable:

有条件地设置一个环境变量:

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

Return a 503 response: RewriteRule's [R]flag can take a non-3xx value and return a non-redirecting response, e.g. for managed downtime/maintenance:

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

RewriteRule .* - [R=503,L]

will return a 503 response (not a redirectper se).

将返回 503 响应(本身不是重定向)。

Also, mod_rewrite can act like a super-powered interface to mod_proxy, so you can do this instead of writing ProxyPassdirectives:

此外,mod_rewrite 可以充当 mod_proxy 的超级接口,因此您可以这样做而不是编写ProxyPass指令:

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

Opinion: Using RewriteRules and RewriteConds to route requests to different applications or load balancers based on virtually any conceivable aspect of the request is just immensely powerful. Controlling requests on their way to the backend, and being able to modify the responses on their way back out, makes mod_rewrite the ideal place to centralize all routing-related config.

意见:使用RewriteRules 和RewriteConds 将请求路由到不同的应用程序或基于请求的几乎任何可以想象的方面的负载均衡器是非常强大的。控制到达后端的请求,并能够在返回的途中修改响应,使 mod_rewrite 成为集中所有路由相关配置的理想场所。

Take the time to learn it, it's well worth it! :)

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