在 Apache 中重定向、更改 URL 或将 HTTP 重定向到 HTTPS - 关于 mod_rewrite 规则你想知道但又不敢问的一切

Kyl*_*ndt 275 mod-rewrite redirect redirection 301-redirect apache-2.2

这是一个关于 Apache 的 mod_rewrite的规范问题

更改请求 URL 或将用户重定向到与他们最初请求的 URL 不同的 URL 是使用 mod_rewrite 完成的。这包括以下内容:

  • 将 HTTP 更改为 HTTPS(或相反)
  • 将不再存在的页面请求更改为新的替换。
  • 修改 URL 格式(例如 ?id=3433 为 /id/3433 )
  • 基于浏览器,基于引用者,基于月亮和太阳下的任何可能呈现不同的页面。
  • 任何你想弄乱 URL 的东西

关于 Mod_Rewrite 规则你想知道但又不敢问的一切!

我怎样才能成为编写 mod_rewrite 规则的专家?

  • mod_rewrite 规则的基本格式和结构是什么?
  • 我需要牢牢掌握正则表达式的什么形式/风格?
  • 编写重写规则时最常见的错误/陷阱是什么?
  • 什么是测试和验证 mod_rewrite 规则的好方法?
  • 我应该注意 mod_rewrite 规则对 SEO 或性能的影响吗?
  • 是否存在 mod_rewrite 看起来是适合这项工作的正确工具但不是的常见情况?
  • 有哪些常见的例子?

测试规则的地方

htaccess的测试网站是玩弄你的规则,并测试他们的好地方。它甚至会显示调试输出,因此您可以查看匹配的内容和不匹配的内容。

sys*_*138 235

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
Run Code Online (Sandbox Code Playgroud)

在这个简单的例子中,如果 HTTP 引用来自 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
Run Code Online (Sandbox Code Playgroud)

所有 .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
Run Code Online (Sandbox Code Playgroud)

但可能应该使用一些更棘手的替换语法来完成。

RewriteEngine On
RewriteCond %{HTTP_REFERER}                ^https?://serverfault\.com(/|$)
RewriteRule ^/blog/(.*)\.(html|jpg)        /blog/$1.sf.$2
Run Code Online (Sandbox Code Playgroud)

更复杂的 RewriteRule 包含处理条件。最后一个括号(html|jpg)告诉 RewriteRule 匹配htmljpg,并在重写的字符串中将匹配的字符串表示为 $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
Run Code Online (Sandbox Code Playgroud)

使用 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
Run Code Online (Sandbox Code Playgroud)

RewriteBase也是特定于订单的,因为它指定了以下RewriteRule指令如何处理它们的处理。它在 .htaccess 文件中非常有用。如果使用,它应该是 .htaccess 文件中“RewriteEngine on”下的第一个指令。拿这个例子:

RewriteEngine On
RewriteBase /blog
RewriteCond %{HTTP_REFERER}           ^https?://serverfault\.com(/|$)
RewriteRule ^(.*)\.(html|jpg)         $1.sf.$2
Run Code Online (Sandbox Code Playgroud)

这告诉 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
Run Code Online (Sandbox Code Playgroud)

如您所见,RewriteBase允许重写规则利用网站内容路径而不是网络服务器,这可以使编辑此类文件的人更容易理解它们。此外,它们可以使指令更短,这具有美学吸引力。


RewriteRule 匹配语法

RewriteRule 本身具有用于匹配字符串的复杂语法。我将在另一节中介绍标志(例如 [PT])。因为系统管理员通过示例学习比阅读手册页更频繁,所以我将给出示例并解释他们的工作。

RewriteRule ^/blog/(.*)$    /newblog/$1
Run Code Online (Sandbox Code Playgroud)

.*构造匹配任何单个字符 ( .) 零次或多次 ( *)。将它括在括号中告诉它提供与 $1 变量匹配的字符串。

RewriteRule ^/blog/.*/(.*)$  /newblog/$1
Run Code Online (Sandbox Code Playgroud)

在这种情况下,第一个 .* 没有包含在括号中,因此不会提供给重写的字符串。此规则删除新博客站点上的目录级别。(/blog/2009/sample.html 变成 /newblog/sample.html)。

RewriteRule ^/blog/(2008|2009)/(.*)$   /newblog/$2
Run Code Online (Sandbox Code Playgroud)

在这种情况下,第一个括号表达式设置了一个匹配组。这变成了 $1,这不是必需的,因此不会在重写的字符串中使用。

RewriteRule ^/blog/(2008|2009)/(.*)$   /newblog/$1/$2
Run Code Online (Sandbox Code Playgroud)

在这种情况下,我们在重写的字符串中使用 $1。

RewriteRule ^/blog/(20[0-9][0-9])/(.*)$   /newblog/$1/$2
Run Code Online (Sandbox Code Playgroud)

此规则使用指定字符范围的特殊括号语法。[0-9] 匹配数字 0 到 9。此特定规则将处理从 2000 年到 2099 年的年份。

RewriteRule ^/blog/(20[0-9]{2})/(.*)$  /newblog/$1/$2
Run Code Online (Sandbox Code Playgroud)

这与前面的规则做同样的事情,但 {2} 部分告诉它匹配前面的字符(在这种情况下是括号表达式)两次。

RewriteRule ^/blog/([0-9]{4})/([a-z]*)\.html   /newblog/$1/$2.shtml
Run Code Online (Sandbox Code Playgroud)

这种情况将匹配第二个匹配表达式中的任何小写字母,并尽可能多地匹配字符。该\.构造告诉它将句点视为实际句点,而不是前面示例中的特殊字符。但是,如果文件名中有破折号,它会中断。

RewriteRule ^/blog/([0-9]{4})/([-a-z]*)\.html  /newblog/$1/$2.shtml
Run Code Online (Sandbox Code Playgroud)

这会捕获带有破折号的文件名。但是,作为-括号表达式中的特殊字符,它必须是表达式中的第一个字符。

RewriteRule ^/blog/([0-9]{4})/([-0-9a-zA-Z]*)\.html   /newblog/$1/$2.shtml
Run Code Online (Sandbox Code Playgroud)

此版本使用字母、数字或文件名中的字符捕获任何文件-名。这是在括号表达式中指定多个字符集的方式。


重写规则标志

重写规则上的标志具有许多特殊含义和用例

RewriteRule ^/blog/([0-9]{4})/([-a-z]*).\html  /newblog/$1/$2.shtml  [L]
Run Code Online (Sandbox Code Playgroud)

标志位于[L]上述表达式的末尾。可以使用多个标志,用逗号分隔。链接的文档描述了每一个,但无论如何它们都在这里:

L = 最后。一旦这个匹配,停止处理 RewriteRules。订单很重要!
C = 链。继续处理下一个 RewriteRule。如果此规则不匹配,则不会执行下一条规则。稍后会详细介绍。
E = 设置环境变量。Apache 有多种环境变量可以影响 Web 服务器的行为。
F = 禁止。如果此规则匹配,则返回 403-Forbidden 错误。
G = 不见了。如果此规则匹配,则返回 410-Gone 错误。
H = 处理程序。强制处理请​​求,就好像它是指定的 MIME 类型一样。
N = 下一个。强制规则重新开始并重新匹配。当心!可能导致循环。
NC = 无。允许jpg匹配jpg和JPG。
NE = 无法逃脱。防止将特殊字符 (. ? # & 等) 重写为它们的十六进制代码等效项。
NS = 无子请求。如果您使用服务器端包含,这将阻止与包含文件的匹配。
P = 代理。强制规则由 mod_proxy 处理。透明地提供来自其他服务器的内容,因为您的网络服务器会获取它并重新提供它。这是一个危险的标志,因为一个写得不好的标志会将您的网络服务器变成一个开放代理,这很糟糕。
PT = 通过。在 RewriteRule 匹配中考虑 Alias 语句。
QSA = QSAppend。当原始字符串包含查询 ( http://example.com/thing?asp=foo) 将原始查询字符串附加到重写的字符串中。通常它会被丢弃。对于动态内容很重要。
R = 重定向。提供到指定 URL 的 HTTP 重定向。还可以提供准确的重定向代码 [R=303]。非常类似于RedirectMatch,它更快,应尽可能使用。
S = 跳过。跳过此规则。
T = 类型。指定返回内容的 MIME 类型。与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
Run Code Online (Sandbox Code Playgroud)

因为第一个 RewriteRule 有 Chain 标志,第二个 rewrite-rule 将在第一个执行时执行,也就是在匹配前一个 RewriteCond 规则时。如果 Apache 正则表达式让你的大脑受到伤害,这很方便。但是,从优化的角度来看,我在第一部分中提到的一体式方法更快。

RewriteRule ^/blog/([0-9]{4})/([-0-9a-zA-Z]*)\.html   /newblog/$1/$2.shtml
Run Code Online (Sandbox Code Playgroud)

这可以通过标志变得更简单:

RewriteRule ^/blog/([0-9]{4})/([-0-9a-z]*)\.html   /newblog/$1/$2.shtml   [NC]
Run Code Online (Sandbox Code Playgroud)

此外,一些标志也适用于 RewriteCond。值得注意的是,NoCase。

RewriteCond %{HTTP_REFERER}        ^https?://serverfault\.com(/|$)     [NC]
Run Code Online (Sandbox Code Playgroud)

将匹配“ServerFault.com”

  • 做得好。[填料] (9认同)
  • 非常好的`mod_rewrite` 和正则表达式入门。+1。 (3认同)
  • 有时知道在匹配“RewriteRule”之后*实际处理“RewriteCond”会很有用。您可能想在靠近顶部的地方说“稍后再谈”,您会说“RewriteCond 之前的 RewriteRule 使该 ONE 规则受条件约束”。您可能想提及正则表达式是与 Perl 兼容的正则表达式。此外,您在“...重写规则认为它是字符串开始...”中有一个无关的撇号 (3认同)
  • `RewriteRule ^/blog/.*/(.*)$ /newblog/$1` 与 *first* 目录组件不匹配 - 默认情况下 rewriterules 是贪婪的。/.*/(.*) 匹配 /1/(2)/ 和 /1/2/3/4/5/(6)/,所以你需要 /[^/]*/ 只匹配第一个路径成分。 (2认同)

dan*_*ree 40

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);
?>
Run Code Online (Sandbox Code Playgroud)

...然后编写您的规则(最好用于在开发服务器上进行测试)并在您的 Apache ErrorLog文件中记录任何不一致的匹配或活动。

对于更复杂的规则,请使用 mod_rewrite 的RewriteLog指令将活动记录到文件并设置RewriteLogLevel 3

我应该注意 mod_rewrite 规则对 SEO 或性能的影响吗?

AllowOverride all影响服务器性能,因为 Apache 必须检查.htaccess文件并解析每个请求的指令 - 如果可能,将所有指令保留在您站点的 VirtualHost 配置中,或者.htaccess仅对需要它们的目录启用覆盖。

Google 的网站管理员指南明确指出:“不要欺骗您的用户或向搜索引擎展示与向用户展示的内容不同的内容,这通常被称为‘伪装’。” - 避免创建 mod_rewrite 指令来过滤搜索引擎机器人。

搜索引擎机器人喜欢一个1:1的内容:URI的映射(这是排名链接内容的基础) -如果你正在使用mod_rewrite创建临时重定向或您所服务在多个URI的内容相同,考虑指定一个标准URI内您的 HTML 文档。

是否存在 mod_rewrite 看起来是适合这项工作的正确工具但不是的常见情况?

这本身就是一个巨大的(并且可能有争议的)话题 - 更好地(恕我直言)根据具体情况解决使用问题,并让提问者确定建议的解决方案是否适合他们的需求。

有哪些常见的例子?

AskApache 的 mod_rewrite Tricks and Tips涵盖了几乎所有定期弹出的常见用例,但是,给定用户的“正确”解决方案可能取决于用户配置和现有指令的复杂程度(这就是为什么它通常是一个每当出现 mod_rewrite 问题时,看看用户有哪些其他指令是个好主意)。


Ter*_*ryE 21

像许多管理员/开发人员一样,我多年来一直在与复杂的重写规则作斗争,并且对现有的 Apache 文档不满意,所以我决定作为一个个人项目深入了解mod_rewrite实际工作以及与 Apache 其余部分的交互核心,所以在过去的几个月里,我一直在使用strace+ 钻探源代码来检测测试用例,以处理所有这些。

以下是重写规则开发人员需要考虑的一些关键注释:

  • 然而,重写的某些方面对于服务器配置、虚拟主机、目录、.htaccess 处理来说是通用的
  • 根配置(服务器配置、虚拟主机和目录)的某些.htaccess处理与 PerDir ( ) 处理截然不同。
  • 更糟糕的是,因为 PerDir 处理几乎可以不加选择地触发 INTERNAL REDIRECT 循环,所以必须编写根配置元素,意识到这种 PerDir 处理可以触发这种情况。

我会说,正因为如此,你几乎需要将重写用户社区分为两类,并将它们视为完全独立的:

  • 那些对 Apache config 具有 root 访问权限的人。这些通常是具有应用程序专用服务器/VM 的管理员/开发人员,这里的信息非常简单:.htaccess尽可能避免使用文件;在您的服务器或虚拟主机配置中执行所有操作。调试非常容易,因为开发人员可以设置调试并可以访问 rewrite.log 文件。

  • 共享托管服务 (SHS) 的用户

    • 此类用户必须使用.htaccess/Perdir 处理,因为没有可用的替代方法。
    • 更糟糕的是,这些用户的技能水平(就使用 mod_rewrite 的正则表达式驱动的梯形逻辑而言)通常明显低于有经验的管理员。
    • Apache 和托管服务提供商不提供调试/诊断支持。唯一的诊断信息是成功的重定向,重定向到错误的 URI。或 404/500 状态代码。这让他们感到困惑和无助。
    • Apache 在解释重写如何适用于这个用例方面非常薄弱。例如,它没有明确说明选择了哪个 PerDir.htaccess文件以及原因。它没有解释 PerDir 循环的复杂性以及如何避免这种情况。

可能还有第三个社区:SHS 提供者的管理和支持人员,他们最终都涉足两个阵营,并不得不承受上述后果。

我已经写了几篇文章风格的博客文章(例如更多关于在 .htaccess 文件中使用重写规则),其中涵盖了很多详细的点,我不会在这里重复以保持这篇文章的简短。我有自己的共享服务,并支持一些专用和 VM FLOSS 项目。我开始使用标准 LAMP VM 作为我的 SHS 帐户的测试工具,但最后我发现最好做一个适当的镜像 VM(在此处描述)。

但是,就管理社区应如何支持.htaccess用户而言,我觉得我们需要开发并提供:

  • 重写系统如何在 PerDir 处理中实际工作的连贯描述
  • 一组关于如何编写.htaccess重写规则的指南/最佳实践
  • 一个简单的基于 web 的重写脚本解析器,类似于 W3C html 解析器,但用户可以通过它输入相同的测试 URI 或测试向量,并立即获得重写逻辑流的日志/
  • 有关如何从规则中获取内置诊断的提示(例如

    • 使用[E=VAR:EXPR]利用这样的事实EXPR将扩大反向引用($ N或N%),以使其可作为诊断的目标脚本。
    • 如果您使用 [OR]、[C]、[SKIP] 和 [L] 标志对重写规则进行局部排序,以便整个重写方案无需利用内部重定向即可工作,那么您可以将以下内容添加为规则 1 以避免所有循环麻烦:

      RewriteCond %{ENV:REDIRECT_STATUS} !=""
      RewriteRule .  -  [L]
      
      Run Code Online (Sandbox Code Playgroud)

  • 你所要做的就是订阅`.htaccess`主题,你就会看到。大多数初学者都非常困惑——他们中的大多数人都是第一次体验 LAMP 服务和共享服务上的 mod_rewrite,因此没有对系统/虚拟主机配置的 root 访问权限,必须通过 `.htaccess` 文件使用每个目录处理。初学者必须“克服”一些重要的差异。我认为自己是一个超级用户,并且仍在发现微妙之处。正如我所说,我不得不使用 strace 和源代码扫描来解决某些方面的问题。应该不需要。:-( (2认同)

Kri*_*ien 15

使用重写映射

使用重写映射可以做很多事情。Rewritemaps 使用 Rewritemap 指令声明,然后可以在 RewritCond 评估和 RewriteRule 替换中使用。

RewriteMap 的一般语法是:

RewriteMap MapName MapType:MapSource
Run Code Online (Sandbox Code Playgroud)

例如:

RewriteMap examplemap txt:/path/to/file/map.txt
Run Code Online (Sandbox Code Playgroud)

然后,您可以将 mapname 用于这样的构造:

${examplemap:key}
Run Code Online (Sandbox Code Playgroud)

该映射包含键/值对。如果找到键,则替换该值。简单映射只是纯文本文件,但您可以使用哈希映射,甚至 SQL 查询。更多细节在文档中:

http://httpd.apache.org/docs/2.2/mod/mod_rewrite.html#rewritemap

非转义字符串。

您可以使用四个内部映射进行一些操作。特别是未转义的字符串可以派上用场。

例如:我想测试查询字符串中的字符串“café”。但是,浏览器会在将其发送到我的服务器之前对其进行转义,因此我需要弄清楚我希望匹配的每个字符串的 URL 转义版本是什么,或者我可以取消转义它......

RewriteMap unescape int:unescape

RewriteCond %{QUERY_STRING}  (location|place)=(.*)
RewriteCond ${unescape:%2}   café
RewriteRule ^/find/$         /find/1234? [L,R]
Run Code Online (Sandbox Code Playgroud)

请注意我如何使用一个 RewriteCond 来捕获查询字符串参数的参数,然后使用第二个 rewriteCond 中的映射来取消转义它。然后进行比较。还要注意我如何需要我们 %2 作为重写映射中的键,因为 %1 将包含“位置”或“地点”。当您使用括号对模式进行分组时,它们也将被捕获,无论您是否打算使用捕获的结果...


bel*_*daz 12

编写重写规则时最常见的错误/陷阱是什么?

一个非常容易的陷阱是当您重写改变明显路径的 URL 时,例如 from /base/1234/index.htmlto /base/script.php?id=1234。客户端将找不到任何具有脚本位置相对路径的图像或 CSS。可以在此常见问题解答中找到许多解决此问题的选项