这是一个典型问题关于 Apache 的 mod_rewrite。
使用 mod_rewrite 可以更改请求 URL 或将用户重定向到与最初请求不同的 URL。这包括以下内容:
- 将 HTTP 更改为 HTTPS(或反之)
- 将对不再存在的页面的请求更改为新的替代页面。
- 修改 URL 格式(例如将 ?id=3433 修改为 /id/3433 )
- 根据浏览器、根据引荐来源、根据月亮和太阳下任何可能的情况呈现不同的页面。
- 任何你想用 URL 来做的事情
您想了解 Mod_Rewrite 规则但又不敢问的一切!
我怎样才能成为编写 mod_rewrite 规则的专家?
- mod_rewrite 规则的基本格式和结构是什么?
- 我需要牢固掌握哪种形式/风格的正则表达式?
- 编写重写规则时最常见的错误/陷阱是什么?
- 测试和验证 mod_rewrite 规则的好方法是什么?
- 我应该注意 mod_rewrite 规则对 SEO 或性能的影响吗?
- 是否存在常见情况,其中 mod_rewrite 看似是适合该工作的工具但实际上并非如此?
- 有哪些常见的例子?
测试你的规则的地方
这htaccess 测试器网站是尝试和测试规则的好地方。它甚至显示调试输出,以便您可以看到哪些匹配,哪些不匹配。
答案1
mod_rewrite 语法顺序
mod_rewrite 有一些影响处理的特定排序规则。在执行任何操作之前,RewriteEngine On
需要给出该指令,因为这会打开 mod_rewrite 处理。这应该在任何其他重写指令之前。
RewriteCond
前面的RewriteRule
规则使该 ONE 规则受条件约束。任何后续的 RewriteRules 都将被处理,就好像它们不受条件约束一样。
RewriteEngine On
RewriteCond %{HTTP_REFERER} ^https?://serverfault\.com(/|$)
RewriteRule $/blog/(.*)\.html $/blog/$1.sf.html
在这个简单的情况下,如果 HTTP referrer 来自 serverfault.com,则将博客请求重定向到特殊的 serverfault 页面(我们就是那么特殊)。但是,如果上面的块有一个额外的 RewriteRule 行:
RewriteEngine On
RewriteCond %{HTTP_REFERER} ^https?://serverfault\.com(/|$)
RewriteRule $/blog/(.*)\.html $/blog/$1.sf.html
RewriteRule $/blog/(.*)\.jpg $/blog/$1.sf.jpg
所有 .jpg 文件都将转到特殊的 serverfault 页面,而不仅仅是那些带有引荐来源表明它来自这里的页面。这显然不是这些规则编写的意图。可以使用多个 RewriteCond 规则来完成:
RewriteEngine On
RewriteCond %{HTTP_REFERER} ^https?://serverfault\.com(/|$)
RewriteRule ^/blog/(.*)\.html /blog/$1.sf.html
RewriteCond %{HTTP_REFERER} ^https?://serverfault\.com(/|$)
RewriteRule ^/blog/(.*)\.jpg /blog/$1.sf.jpg
但可能应该采用一些更棘手的替换语法。
RewriteEngine On
RewriteCond %{HTTP_REFERER} ^https?://serverfault\.com(/|$)
RewriteRule ^/blog/(.*)\.(html|jpg) /blog/$1.sf.$2
更复杂的 RewriteRule 包含处理的条件。最后一个括号(html|jpg)
告诉 RewriteRule 匹配html
或jpg
,并在重写字符串中将匹配的字符串表示为 $2。这在逻辑上与上一个块相同,有两个 RewriteCond/RewriteRule 对,只是它在两行而不是四行上执行。
多个 RewriteCond 行隐式进行 AND 运算,也可以显式进行 OR 运算。要处理来自 ServerFault 和超级用户的引荐来源(显式 OR):
RewriteEngine On
RewriteCond %{HTTP_REFERER} ^https?://serverfault\.com(/|$) [OR]
RewriteCond %{HTTP_REFERER} ^https?://superuser\.com(/|$)
RewriteRule ^/blog/(.*)\.(html|jpg) /blog/$1.sf.$2
使用 Chrome 浏览器提供 ServerFault 引用的页面(隐式 AND):
RewriteEngine On
RewriteCond %{HTTP_REFERER} ^https?://serverfault\.com(/|$)
RewriteCond %{HTTP_USER_AGENT} ^Mozilla.*Chrome.*$
RewriteRule ^/blog/(.*)\.(html|jpg) /blog/$1.sf.$2
RewriteBase
也是特定顺序的,因为它指定了后续RewriteRule
指令如何处理它们。它在 .htaccess 文件中非常有用。如果使用,它应该是 .htaccess 文件中“RewriteEngine on”下的第一个指令。请看这个例子:
RewriteEngine On
RewriteBase /blog
RewriteCond %{HTTP_REFERER} ^https?://serverfault\.com(/|$)
RewriteRule ^(.*)\.(html|jpg) $1.sf.$2
这告诉 mod_rewrite 它当前正在处理的这个特定 URL 是通过http://example.com/blog/而不是物理目录路径 (/home/$Username/public_html/blog),并相应地处理它。因此,它将RewriteRule
字符串开头视为 URL 中的“/blog”之后。以下是同一件事的两种不同写法。一个使用 RewriteBase,另一个不使用:
RewriteEngine On
##Example 1: No RewriteBase##
RewriteCond %{HTTP_REFERER} ^https?://serverfault\.com(/|$)
RewriteRule /home/assdr/public_html/blog/(.*)\.(html|jpg) $1.sf.$2
##Example 2: With RewriteBase##
RewriteBase /blog
RewriteCond %{HTTP_REFERER} ^https?://serverfault\.com(/|$)
RewriteRule ^(.*)\.(html|jpg) $1.sf.$2
如您所见,RewriteBase
允许重写规则来利用网络地点内容路径,而不是网页路径服务器,这可以使编辑此类文件的人更容易理解。此外,它们还可以使指令更短,具有美感。
RewriteRule 匹配语法
RewriteRule 本身具有用于匹配字符串的复杂语法。我将在另一节中介绍标志(例如 [PT])。因为系统管理员通过示例学习的次数多于通过阅读手册页我将举例并解释它们的作用。
RewriteRule ^/blog/(.*)$ /newblog/$1
该.*
构造匹配任何单个字符 ( .
) 零次或多次 ( *
)。将其括在括号中告诉它提供匹配的字符串作为 $1 变量。
RewriteRule ^/blog/.*/(.*)$ /newblog/$1
在这种情况下,第一个 .* 没有括在括号中,因此不会提供给重写的字符串。此规则会删除新博客网站上的目录级别。(/blog/2009/sample.html 变为 /newblog/sample.html)。
RewriteRule ^/blog/(2008|2009)/(.*)$ /newblog/$2
在这种情况下,第一个括号表达式设置了一个匹配组。这变成了 $1,但它是不需要的,因此在重写的字符串中不会使用。
RewriteRule ^/blog/(2008|2009)/(.*)$ /newblog/$1/$2
在这种情况下,我们在重写的字符串中使用 $1。
RewriteRule ^/blog/(20[0-9][0-9])/(.*)$ /newblog/$1/$2
此规则使用特殊的括号语法来指定字符范围. [0-9] 匹配数字 0 到 9。此特定规则将处理从 2000 年到 2099 年的年份。
RewriteRule ^/blog/(20[0-9]{2})/(.*)$ /newblog/$1/$2
这与上一个规则的作用相同,但是 {2} 部分告诉它匹配前一个字符(在本例中为括号表达式)两次。
RewriteRule ^/blog/([0-9]{4})/([a-z]*)\.html /newblog/$1/$2.shtml
此案例将匹配第二个匹配表达式中的任何小写字母,并匹配尽可能多的字符。构造\.
告诉它将句点视为实际句点,而不是前面示例中的特殊字符。但是,如果文件名中有破折号,它将中断。
RewriteRule ^/blog/([0-9]{4})/([-a-z]*)\.html /newblog/$1/$2.shtml
这会捕获带有破折号的文件名。但是,作为-
括号表达式中的特殊字符,它必须是第一的表达式中的字符。
RewriteRule ^/blog/([0-9]{4})/([-0-9a-zA-Z]*)\.html /newblog/$1/$2.shtml
此版本会捕获任何包含字母、数字或-
文件名中的字符的文件名。这就是在括号表达式中指定多个字符集的方法。
重写规则标志
RewriteRule ^/blog/([0-9]{4})/([-a-z]*).\html /newblog/$1/$2.shtml [L]
标志位于[L]
上述表达式的末尾。可以使用多个标志,以逗号分隔。链接的文档描述了每个标志,但无论如何它们在这里:
大号= 最后一条。一旦匹配,停止处理 RewriteRules。顺序很重要!
C= Chain。继续处理下一个 RewriteRule。如果此规则不匹配,则不会执行下一个规则。稍后会详细介绍。
埃= 设置环境变量。Apache 有各种环境变量,可以影响 Web 服务器的行为。
F= 禁止。如果此规则匹配,则返回 403-Forbidden 错误。
G= Gone。如果此规则匹配,则返回 410-Gone 错误。
H= 处理程序。强制按照指定的 MIME 类型来处理请求。
否= 下一步。强制规则重新开始并重新匹配。小心!可能导致循环。
数控= 无大小写。允许jpg
匹配 jpg 和 JPG。
东北= 无转义。防止将特殊字符(. ? # & 等)重写为其十六进制代码等效项。
国家标准= 无子请求。如果您使用服务器端包含,这将阻止与包含的文件匹配。
磷= 代理。强制规则由 mod_proxy 处理。透明地提供来自其他服务器的内容,因为您的 Web 服务器会获取并重新提供它。这是一个危险的标志,因为写得不好的标志会将您的 Web 服务器变成开放代理,这很糟糕。
太平洋标准时间= 传递。在 RewriteRule 匹配中考虑 Alias 语句。
定量分析= QSAppend。当原始字符串包含查询(http://example.com/thing?asp=foo)将原始查询字符串附加到重写的字符串。通常会将其丢弃。对于动态内容很重要。
R= 重定向。提供到指定 URL 的 HTTP 重定向。还可以提供精确的重定向代码 [R=303]。与 非常相似RedirectMatch
,后者速度更快,应尽可能使用。
年代= 跳过。跳过此规则。
电视= 类型。指定返回内容的 mime-type。与AddType
指令非常相似。
您知道我说过这只RewriteCond
适用于一条规则吗?好吧,您可以通过链接来解决这个问题。
RewriteEngine On
RewriteCond %{HTTP_REFERER} ^https?://serverfault\.com(/|$)
RewriteRule ^/blog/(.*)\.html /blog/$1.sf.html [C]
RewriteRule ^/blog/(.*)\.jpg /blog/$1.sf.jpg
因为第一个 RewriteRule 有 Chain 标志,所以第二个重写规则将在第一个重写规则执行时执行,也就是当匹配上一个 RewriteCond 规则时。如果 Apache 正则表达式让您头疼,那么这种方法就很方便了。但是,从优化的角度来看,我在第一部分中指出的一行代码方法速度更快。
RewriteRule ^/blog/([0-9]{4})/([-0-9a-zA-Z]*)\.html /newblog/$1/$2.shtml
这可以通过标志变得更简单:
RewriteRule ^/blog/([0-9]{4})/([-0-9a-z]*)\.html /newblog/$1/$2.shtml [NC]
此外,一些标志也适用于 RewriteCond。值得注意的是 NoCase。
RewriteCond %{HTTP_REFERER} ^https?://serverfault\.com(/|$) [NC]
将匹配“ServerFault.com”
答案2
mod_rewrite 规则的基本格式和结构是什么?
我将参考 sysadmin1138 对这些问题的精彩回答。
我需要牢固掌握哪种形式/风格的正则表达式?
除了 sysadmin1138 概述的语法顺序、语法匹配/正则表达式和 RewriteRule 标志之外,我认为值得一提的是,mod_rewrite 会根据 HTTP 请求标头和 Apache 的配置公开 Apache 环境变量。
我会推荐AskApache 的 mod_rewrite 调试教程以获取 mod_rewrite 可用的变量的完整列表。
编写重写规则时最常见的错误/陷阱是什么?
RewriteRule 的大多数问题源于对 PCRE 语法的误解/未能正确转义特殊字符或对用于匹配的变量内容缺乏了解。
典型问题及建议故障排除:
- 500内部服务器错误-删除 Windows 回车控制在配置文件中(如果存在),确保 mod_rewrite 已启用(将指令包装在
IfModule
有条件地避免这种情况),检查指令语法,注释掉指令,直到发现问题 - 重定向循环- 利用 RewriteLog 和 RewriteLogLevel,注释掉指令,直到问题被识别
测试和验证 mod_rewrite 规则的好方法是什么?
首先,查看您计划匹配的环境变量的内容 - 如果您安装了 PHP,这就像向您的应用程序添加以下块一样简单:
<?php
var_dump($_SERVER);
?>
...然后编写规则(最好在开发服务器上进行测试)并记下 Apache 中任何不一致的匹配或活动错误日志文件。
对于更复杂的规则,请使用 mod_rewrite 的RewriteLog
指令将活动记录到文件并设置RewriteLogLevel 3
我应该注意 mod_rewrite 规则对 SEO 或性能的影响吗?
AllowOverride all
影响服务器性能,因为 Apache 必须对.htaccess
每个请求检查文件并解析指令 - 如果可能的话,请将所有指令保留在您网站的 VirtualHost 配置中,或.htaccess
仅为需要它们的目录启用覆盖。
谷歌的网站站长指南明确指出:“不要欺骗你的用户,也不要向搜索引擎展示与你向用户显示的内容不同的内容,这通常被称为‘伪装’。”——避免创建过滤搜索引擎机器人的 mod_rewrite 指令。
搜索引擎机器人更喜欢 1:1 内容:URI 映射(这是对内容链接进行排名的基础) - 如果您使用 mod_rewrite 创建临时重定向,或者在多个 URI 下提供相同的内容,请考虑指定规范 URI在您的 HTML 文档中。
是否存在常见情况,其中 mod_rewrite 看似是适合该工作的工具但实际上并非如此?
这本身就是一个巨大的(并且可能引起争议的)话题 - 更好的是(IMHO)根据具体情况解决用途,让提问者确定建议的解决方案是否适合他们的需要。
有哪些常见的例子?
AskApache 的 mod_rewrite 技巧和提示几乎涵盖了经常出现的所有常见用例,但是,对于给定的用户,“正确”的解决方案可能取决于用户配置和现有指令的复杂程度(这就是为什么通常最好查看哪些其他每当出现 mod_rewrite 问题时用户所采取的指令)。
答案3
与许多管理员/开发人员一样,多年来我一直在与重写规则的复杂性作斗争,并且对现有的 Apache 文档感到不满意,因此我决定作为一个个人项目来彻底了解它的mod_rewrite
实际工作原理以及与 Apache 核心的其余部分的交互方式,所以在过去的几个月里,我一直在使用测试用例并strace
深入研究源代码以掌握所有这些内容。
以下是重写规则开发人员需要考虑的一些关键评论:
- 重写的某些方面对于服务器配置、虚拟主机、目录、.htaccess 处理是通用的然而
- 与 PerDir () 处理相比,根配置(服务器配置、虚拟主机和目录)的一些处理非常不同
.htaccess
。 - 更糟糕的是,因为 PerDir 处理几乎可以不加区分地触发 INTERNAL REDIRECT 循环,所以必须在写入根配置元素时意识到这种 PerDir 处理可以触发这种情况。
我甚至可以说,正因为如此,你几乎需要将重写用户社区分成两类,并将它们视为完全独立:
具有 Apache 配置 root 访问权限的用户。这些通常是具有应用程序专用服务器/VM 的管理员/开发人员,这里的信息非常简单:
.htaccess
尽可能避免使用文件;在服务器或 vhost 配置中执行所有操作。调试相当容易,因为开发人员可以设置调试并可以访问 rewrite.log 文件。共享托管服务 (SHS) 的用户。
- 此类用户有使用
.htaccess
/Perdir 处理,因为没有其他可用方法。 - 更糟糕的是,此类用户的技术水平(就使用 mod_rewrite 的正则表达式驱动的阶梯逻辑而言)通常明显低于经验丰富的管理员。
- Apache 和托管提供商不提供调试/诊断支持。唯一的诊断信息是成功重定向、重定向到错误的 URI 或 404/500 状态代码。这让他们感到困惑和无助。
- Apache 在解释此用例的重写工作原理方面非常薄弱。例如,它没有清楚地解释
.htaccess
选择了哪个 PerDir 文件以及原因。它没有解释 PerDir 循环的复杂性以及如何避免这种情况。
- 此类用户有使用
可能还存在第三个群体:SHS 提供商的管理人员和支持人员,他们最终处于两个阵营之中,并且必须承受上述后果。
我写过几篇博客文章(例如有关在 .htaccess 文件中使用重写规则的更多信息) 涵盖了很多细节点,为了保持这篇文章简短,我不会在这里重复。我有自己的共享服务,并支持一些专用和 VM FLOSS 项目。我开始使用标准 LAMP VM 作为我的 SHS 帐户的测试工具,但最后我发现最好做一个适当的镜像 VM(描述这里)。
然而,就管理员社区应如何支持用户而言.htaccess
,我觉得我们需要开发和提供:
- 关于重写系统在 PerDir 处理中实际如何工作的连贯描述
- 关于如何编写
.htaccess
重写规则的一组指南/最佳实践 - 一种简单的基于 Web 的重写脚本解析器,有点类似于 W3C html 解析器,但用户可以输入相同的测试 URI 或测试向量并立即获得重写逻辑流的日志/
关于如何从规则中获取内置诊断的提示(例如
- 利用扩展反向引用($N 或 %N)
[E=VAR:EXPR]
的事实EXPR
,使它们可作为目标脚本的诊断信息。 如果你使用 [OR]、[C]、[SKIP] 和 [L] 标志按主题对重写规则进行排序,以便整个重写方案有效没有需要利用内部重定向,那么您可以将以下内容添加为规则 1,以避免所有循环麻烦:
RewriteCond %{ENV:REDIRECT_STATUS} !="" RewriteRule . - [L]
- 利用扩展反向引用($N 或 %N)
答案4
编写重写规则时最常见的错误/陷阱是什么?
一个非常容易犯的错误是,当你重写 URL 时,会改变明显的路径,例如从
/base/1234/index.html
到/base/script.php?id=1234
。客户端将无法找到任何具有相对于脚本位置的路径的图像或 CSS。可以在以下位置找到许多解决此问题的选项:此常见问题解答。