调试.htaccess重写规则的提示


272

许多发布者在调试文件中的RewriteRule和RewriteCond语句时遇到问题.htaccess。其中大多数使用共享主机服务,因此无权访问根服务器配置。他们无法避免使用.htaccess文件进行重写,也无法启用RewriteLogLevel”,正如许多受访者所建议的那样。此外,还有很多.htaccess特定的陷阱和约束没有得到很好的涵盖。对于大多数人来说,设置本地测试LAMP堆栈涉及太多的学习曲线。

所以我的Q这里是如何将我们建议他们调试他们的规则本身。我在下面提供一些建议。其他建议,将不胜感激。

  1. 了解mod_rewrite引擎在.htaccess文件中循环。引擎运行以下循环:

    do
      execute server and vhost rewrites (in the Apache Virtual Host Config)
      find the lowest "Per Dir" .htaccess file on the file path with rewrites enabled
      if found(.htaccess)
         execute .htaccess rewrites (in the user's directory)
    while rewrite occurred
    

    因此,您的规则将重复执行,如果您更改URI路径,则它可能最终会执行其他.htaccess文件(如果存在)。因此,如有必要,请通过添加其他内容RewriteCond来停止触发规则来确保终止此循环。.htaccess除非明确打算使用多级规则集,否则还请删除所有较低级的重写规则集。

  2. 通过对一组测试模式进行测试,确保每个Regexp的语法正确,以确保这是一种有效的语法,并使用完整的测试URI来达到您的预期。有关更多详细信息,请参见下面答案

  3. 在测试目录中逐步建立规则。 您可以使用“ .htaccess在路径功能上执行最深的文件”来在此处设置单独的测试目录(树)并调试规则集,而无需弄乱您的主要规则并停止站点的工作。您必须一次添加一个,因为这是将故障定位到单个规则的唯一方法。

  4. 使用虚拟脚本存根转储服务器和环境变量。(请参见清单2),例如,如果您的应用程序使用,blog/index.php则可以将其复制到其中test/blog/index.php并使用它来测试test子目录中的博客规则。您还可以使用环境变量来确保重写引擎正确解释替换字符串,例如

    RewriteRule ^(.*) - [E=TEST0:%{DOCUMENT_ROOT}/blog/html_cache/$1.html]
    

    并在phpinfo转储中查找这些REDIRECT_ *变量。顺便说一句,我使用了这个,并在我的网站上发现我不得不使用它%{ENV:DOCUMENT_ROOT_REAL}。对于重定向器循环, REDIRECT_REDIRECT_ *变量将列出前一遍。等等..

  5. 确保您不会被浏览器缓存不正确的301重定向所困扰。请参阅下面的答案。我感谢Ulrich Palha

  6. 重写引擎似乎对.htaccess上下文中的级联规则很敏感(这是RewriteRule导致替换的地方,但这属于进一步的规则),因为我发现内部子请求(1)有错误,并且PATH_INFO处理不正确通过使用[NS],[L]和[PT]标志来防止。

还有其他意见或建议吗?

清单1-phpinfo

<?php phpinfo(INFO_ENVIRONMENT|INFO_VARIABLES);

10
这些很好...也许您应该将它们从问题转移到答案。
w00t 2012年

@ w00t,我已根据您的建议拆分了正则表达式检查器,因为我想通过其他答案中的链接来引用它。
TerryE '02

3
您可能希望将文档中的控制流程图添加到第一个建议中。IMO比任何伪代码或解释都容易理解,这实际上是mod-rewrite伏都教徒最黑的部分。
国家税务总局

第6个数字很大。在标准apache配置文件中与.htaccess文件中不同的重写规则必须吸引很多人。
伊恩·柯林斯

可能值得在这些提示中添加一些内容:我花了一些时间调试重定向和不重写的问题。原来,当我想要“ / comment /”时,我将其重写为“ / comment”。它被重写为“ / comment”,然后服务器正在重定向到“ / comment /”。与以前使用Apache的人相比,行为明显,但对于像我这样的菜鸟来说,可能更少。
克里斯

Answers:


132

以下是有关测试规则的其他一些技巧,这些技巧可以简化共享主机上用户的调试

1.使用虚假用户代理

测试新规则时,添加条件以仅fake使用将用于请求的用户代理执行它。这样,它不会影响您网站上的其他任何人。

例如

#protect with a fake user agent
RewriteCond %{HTTP_USER_AGENT}  ^my-fake-user-agent$
#Here is the actual rule I am testing
RewriteCond %{HTTP_HOST} !^www\.domain\.com$ [NC] 
RewriteRule ^ http://www.domain.com%{REQUEST_URI} [L,R=302] 

如果使用的是Firefox,则可以使用User Agent Switcher创建伪造的User Agent字符串并进行测试。

2.在完成测试之前,请勿使用301

我见过很多帖子,人们仍在测试他们的规则,他们使用301。不要

如果您未在网站上使用建议1,则301不仅会影响您,而且还会影响当时访问您网站的任何人。

请记住,它们是永久的,并且会被您的浏览器主动缓存。直到您确定之前使用302,然后将其更改为301。

3.请记住,301已被积极地缓存在您的浏览器中

如果您的规则不起作用,并且看起来很合适,并且您没有使用建议1和2,则请在清除浏览器缓存后或在私有浏览中进行重新测试。

4.使用HTTP捕获工具

使用Fiddler之类的HTTP捕获工具查看浏览器和服务器之间的实际HTTP流量。

尽管其他人可能会说您的问题site does not look right,但您可以查看并报告all of the images, css and js are returning 404 errors该问题,从而迅速缩小问题范围。

尽管其他人会向您报告,但started at URL A and ended at URL C您将能够看到他们始于URL A, were 302 redirected to URL B and 301 redirected to URL C。即使URL C是最终目标,您也会知道这对SEO不利,需要进行修复。

您将能够看到在服务器端设置的缓存头,重播请求,修改请求头以进行测试...。



9
Ulrich,非常感谢您的投入。您已经掌握了一些我没想到要列入清单的方面。关于301调试问题,我在“私人浏览”(又称“色情模式”)中使用Chrome,因为这会在您关闭窗口时转储此状态信息。我希望您不要介意我不“接受”这是很重要的一点,但不是一个最佳答案。再次感谢。:)
TerryE '02

1
为了清楚[L,R=302]
起见

6
您无需明确指定[L, R=302]只是执行[L,R]默认操作即可302
Rahil Wazir 2014年

2
@goodeye,还要查看“ Chrome>设置>常规>在打开DevTools时禁用缓存”复选框。
johnsnails

83

在线.htaccess重写测试

我找到 Googling for RegEx帮助,.htaccess每次进行小小的修改后,就不必再上传新文件,这为我节省了很多时间。

从站点:

htaccess测试仪

要测试htaccess重写规则,只需填写要对其应用规则的url,将htaccess的内容放在较大的输入区域,然后按“立即检查”按钮。


6
感谢您指向此工具的指针,我找到了调试该问题的最直接方法。
BobHy

如果您可以通过ssh访问您的网站空间,则另一个选择是直接通过服务器上的编辑器更改.htaccess。
sjas

如果站点证书有问题,您将需要忽略ssl警告b / c。但是该站点仍然存在。这是最好,最简单的解决方案。它提供了令人难以置信的洞察力,可以找出问题所在,并快速解决问题。
toddmo '18 -4-26

感谢您指向此工具。这很有用,有时调试htaccess本身太难了。谢谢。非常感谢
Benyamin Limanto

似乎所引用的链接有错误,并不能始终为您提供确切的输出。请绝对检查实际的Apache。
Parth

13

不要忘记,在.htaccess文件中,它是一个匹配的相对URL。

在.htaccess文件中,以下RewriteRule将永远不匹配:

RewriteRule ^/(.*)     /something/$s

4
是供给到一个重写字符串规则是相对的,因此剥离的任何前导/,但这剥离不适用于在重写组装匹配串发生电导率命令。
TerryE

8

确保每个Regexp的语法正确

通过针对一组测试模式进行测试,以确保这是一种有效的语法,并使用完整的测试URI来达到您的预期。

请参阅下面的regexpCheck.php,获取一个简单的脚本,您可以将其添加到站点的private / test目录中以帮助您完成此操作。我保持简短而不是漂亮。只需将其放入regexpCheck.php测试目录中的文件中,即可在您的网站上使用它。这将帮助您构建任何正则表达式,并在测试用例列表中对其进行测试。我在这里使用PHP PCRE引擎,但是看过Apache的源代码,这基本上与Apache中使用的源代码相同。有许多HowTo和教程提供了模板,可以帮助您建立正则表达式技能。

清单1-regexpCheck.php

<html><head><title>Regexp checker</title></head><body>
<?php 
    $a_pattern= isset($_POST['pattern']) ? $_POST['pattern'] : "";
    $a_ntests = isset($_POST['ntests']) ? $_POST['ntests'] : 1;
    $a_test   = isset($_POST['test']) ? $_POST['test'] : array();
    
    $res = array(); $maxM=-1; 
    foreach($a_test as $t ){
        $rtn = @preg_match('#'.$a_pattern.'#',$t,$m);
        if($rtn == 1){
            $maxM=max($maxM,count($m));
            $res[]=array_merge( array('matched'),  $m );
        } else {
            $res[]=array(($rtn === FALSE ? 'invalid' : 'non-matched'));
        }
    } 
?> <p>&nbsp; </p>
<form method="post" action="<?php echo $_SERVER['SCRIPT_NAME'];?>">
    <label for="pl">Regexp Pattern: </label>
    <input id="p" name="pattern" size="50" value="<?php echo htmlentities($a_pattern,ENT_QUOTES,"UTF-8");;?>" />
    <label for="n">&nbsp; &nbsp; Number of test vectors: </label>
    <input id="n" name="ntests"  size="3" value="<?php echo $a_ntests;?>"/>
    <input type="submit" name="go" value="OK"/><hr/><p>&nbsp;</p>
    <table><thead><tr><td><b>Test Vector</b></td><td>&nbsp; &nbsp; <b>Result</b></td>
<?php 
    for ( $i=0; $i<$maxM; $i++ ) echo "<td>&nbsp; &nbsp; <b>\$$i</b></td>";
    echo "</tr><tbody>\n";
    for( $i=0; $i<$a_ntests; $i++ ){
        echo '<tr><td>&nbsp;<input name="test[]" value="', 
            htmlentities($a_test[$i], ENT_QUOTES,"UTF-8"),'" /></td>';
        foreach ($res[$i] as $v) { echo '<td>&nbsp; &nbsp; ',htmlentities($v, ENT_QUOTES,"UTF-8"),'&nbsp; &nbsp; </td>';}
        echo "</tr>\n";
    }
?> </table></form></body></html>

1
快速说明:import_request_variables在PHP 5.3中已弃用,在5.4中已删除。 extract($_GET)加上extract($_POST)可以执行相同的功能,但是所有变量都需要从其名称中删除前缀。来源:php.net/manual/en/function.import-request-variables.php
Jeff Lambert

@watcher,谢谢。我在一年前已将本地版本更新为5.4兼容,但是忘记更改此帖子。现在完成。
TerryE

哦,我的即使编辑后也无法仅通过复制代码来获得良好的结果...但是随着regex提琴手的到来,我认为您的工具已经过时了。看看这些很棒的工具:regex101.comrefiddle.comregexr.com
hexerei软件

@hexereisoftware,此文章已有3年历史,因此可能会存在一些细微问题,具体取决于当前使用的PHP版本和Apache版本。但是,regexp有许多变体,每个变体都有细微的差异。正如我所说的,Apache代码使用的PCRE引擎与PHP引擎非常相似。我不确定与.net等其他变量的区别是什么,因此,尽管您建议使用在线资源是一个很好的建议,但我会坚持使用明确支持apache或PHP语法的建议。:-)
TerryE

Perl将是最接近的,但是php使用相同的语法
hexerei软件

7

确保在变量前面使用百分号,而不是美元符号。

%{HTTP_HOST}不是 ${HTTP_HOST}。error_log中将没有任何内容,也不会出现内部服务器错误,您的regexp仍然正确,该规则将不匹配。如果您经常使用django / genshi模板并${}在肌肉记忆中进行变量替换,那么这真是可怕。


1
是的,$替换变量与最后一个RewriteRule模式有关,替换变量与最后 一个RewriteCond模式有关,诸如%{env:XXX}
TerryE'9

7

我浪费了几个小时的一个:

如果您已应用所有这些技巧,并且由于无法访问服务器错误日志而仅发生500个错误,则问题可能不在.htaccess中,而是在它重定向到的文件中。

修复.htaccess问题后,尽管我只是忘记了一些权限,但我还是花了两个多小时试图对其进行修复。


我为我的个人站点使用共享访问托管Web服务,但是我要做的是设置一个测试虚拟机,该虚拟机在PHP / Apache配置,主目录等方面大致反映了这一点。管理员我可以启用重写日志记录以诊断任何困难的.htaccess问题。
TerryE

6

设置环境变量并使用标头接收它们:

如OP所述,您可以使用RewriteRule行创建新的环境变量:

RewriteRule ^(.*) - [E=TEST0:%{DOCUMENT_ROOT}/blog/html_cache/$1.html]

但是,如果无法使服务器端脚本正常工作,那么如何读取此环境变量?一种解决方案是设置标头:

Header set TEST_FOOBAR "%{REDIRECT_TEST0}e"

该值接受格式说明符,包括%{NAME}e环境变量的说明符(请不要忘记小写字母e)。有时,您需要添加REDIRECT_前缀,但是何时添加前缀以及何时不添加前缀,我还没有解决。


您是否何时使用REDIRECT_前缀有了进一步的了解?另外,我也看到了有关其他(htaccess)上下文中的前缀的术语,但目前尚不清楚其确切含义。这是否意味着在使用某些命令(而不是其他命令)时,必须使用前缀来命名变量或将前缀添加到命名变量中?你举的例子是第一次昭示着双方的VAR定义和VAR的使用,所以从这个我倾向于认为后者!该文档几乎没有帮助-他们假设我们知道太多,并且给出的参考文献/链接太少。
SherylHohman

5

如果要创建重定向,请使用curl测试以避免浏览器缓存问题。使用-I仅获取http标头。使用-L跟随所有重定向。


3

我在尝试调试mod_rewrite问题时发现了这个问题,它肯定有一些有用的建议。但最后最重要的是确保您的正则表达式语法正确。由于我自己的RE语法存在问题,因此无法安装regexpCheck.php脚本。

但是,由于Apache使用与Perl兼容的正则表达式(PCRE),因此任何有助于编写PCRE的工具都应有所帮助。过去,我将RegexPlanet的工具与Java和Javascript RE一起使用,并很高兴发现它们也支持Perl。

只需输入您的正则表达式和一个或多个示例URL,它就会告诉您正则表达式是否匹配(“〜=“列中的” 1“)以及(如果适用)任何匹配的组(“拆分”中的数字)列将对应于Apache期望的数字,例如每个URL的$ 1,$ 2等。他们声称PCRE支持是“测试版”,但这正是我解决语法问题所需要的。

http://www.regexplanet.com/advanced/perl/index.html

我只是在现有答案中添加评论,但我的声誉还没有达到这个水平。希望这对某人有帮助。


不错的工具,但形式糟糕……请查看以下出色工具:regex101.comrefiddle.comregexr.com
hexerei软件

3

关于4.,在完成所有重写之后,您仍然需要确保“虚拟脚本存根”实际上是目标URL,否则您将看不到任何东西!

一个类似/相关的技巧(请参阅此问题)是插入一个临时规则,例如:

RewriteRule (.*) /show.php?url=$1 [END]

show.php一些非常简单的脚本在哪里显示$_GET参数(如果需要,您也可以显示环境变量)。

这将在您将其插入到规则集中的那一刻停止重写,就像调试器中的断点一样。

如果您使用的是Apache <2.3.9,则需要使用[L]而不是[END],然后可能需要添加:

RewriteRule ^show.php$ - [L]

如果 URL /show.php本身在被重写,在规则集的最顶部。


3

我观察到的一些错误是在写作时发生的 .htaccess

^(.*)$在多个规则中重复使用^(.*)$在大多数情况下会导致其他规则无效,因为它与单个匹配中的所有网址都匹配。

因此,如果我们对此URL使用rule,sapmle/url它将也消耗该URL sapmle/url/string


[L] 标志应用于确保我们的规则已完成处理。


应该知道:

%n和$ n的差异

%n%{RewriteCond}部分$n匹配,%{RewriteRule}部分匹配。

RewriteBase的工作

RewriteBase伪指令指定要用于替换相对路径的每个目录(htaccess)RewriteRule伪指令使用的URL前缀。

在每个目录(htaccess)上下文中的替换中使用相对路径时,除非满足以下任一条件,否则此指令是必需的:

原始请求和替代请求位于DocumentRoot(与其他方法(例如Alias)可访问的对象)相比之下。包含RewriteRule的目录的文件系统路径(由相对替换后缀)也可以作为服务器上的URL路径有效(这种情况很少见)。在Apache HTTP Server 2.4.16和更高版本中,当通过Alias或mod_userdir映射请求时,可以忽略此伪指令。


2

如果您打算用.htacesss编写多行规则,
甚至不用考虑尝试使用一种热修复方法对其进行调试。

我浪费了很多时间来设置多个规则,而没有LOG的反馈,只是最终放弃了。
我在PC上安装了Apache,将整个站点复制到其HDD,然后使用日志将整个规则集整理得非常快。
然后,我查看了我的旧规则,这些规则一直在起作用。我看到他们没有真正按照要求做。定时炸弹,地址略有不同。

重写规则有很多陷阱,这根本不是逻辑上的事情。
您可以在十分钟内启动并运行Apache,它的大小为10MB,许可证不错,* NIX / WIN / MAC就绪,即使没有安装也是如此。
另外,检查服务器的标题行,如果存档较旧,则从其存档中获取相同版本的Apache。我的OP仍为2.0;不支持很多东西。


papo,我在开发结构中运行了专用服务器,ISP托管的VPS和私有VM,但是我仍然为我的公共域和电子邮件使用共享托管服务,只是因为使用完全托管的托管更加方便且具有成本效益为这些服务。此方法实际上是针对共享服务用户的。配置私有VM以完全镜像共享服务是困难的。是的,如果您可以使用测试VM会有所帮助,但是我仍然不时在共享服务上使用这些“技巧”。
TerryE

1
如果您将A用作调试mod_rewrite规则的替代建议,那么我会对此表示同意,但是对于那些努力了解为什么htaccess文件不起作用的基本共享服务用户而言,“甚至不用考虑”这句话简直是不好的建议。按照他们应有的方式工作。
TerryE

对不起,如果听起来您的工作没有做很多事情,那就是毫无价值。我不想要那样 相信我,我非常高兴阅读并关注此线程提供的许多建议。但是我的规则慢慢变得复杂起来,最后,我只是不想解决安装Apache服务器并进行应有的调试工作而浪费了很多时间。不仅如此,我还没有从日志中学到什么,正如我所没有看到的那样。而且有很多事情正在进行。我相信分享这一经验也很有价值。
papo

对于第二部分,有一个IF。我的文字从未以“我什至不考虑”开头,现在看来,这个措词似乎有些苛刻,但这是真的。特别是对于那些对此感到陌生并且难以理解的人。这里的建议可能像我一样误导他们,我只需要一个可靠的正则表达式,就不是那么简单,就像您所说的那样6)PATH_INFO给我带来了很多麻烦,这不是您所说的错误,而是一个功能。如果您不想重新添加它,请使用[DPI]。但是只有当您查看日志时,您才会看到它被添加到那里。这就是为什么多于一行,而您最好使用原木
papo

1
@papo,很抱歉,但是我以-1票的原因是,我认为这“甚至不用考虑”是不好的建议,IMO。如果您的意思是“具有一定的复杂度,那么您可能会发现安装本地Apache服务来调试.htaccess文件更容易”,那么这会更加平衡。是的,设置本地Apache服务相当容易,但是要使其反映服务提供商的共享托管服务可能会很复杂,而且超出了许多可能只是使用一键式设置Wordpress的用户的技能水平,说,并且.htaccess文件有问题。
TerryE

1

我将在这里,可能保留明显的细节,但要让%{REQUEST_URI}几个小时:小心使用,因为@Krist van Besien在他的回答中说的是完全正确的,但对于REQUEST_URI字符串却不正确,因为这样做的结果TestString以开头/。因此请注意:

RewriteCond %{REQUEST_URI} ^/assets/$  
                            ^
                            | check this pesky fella right here if missing

0

(类似于Doin的想法)为了显示匹配的内容,我使用以下代码

$keys = array_keys($_GET);
foreach($keys as $i=>$key){
    echo "$i => $key <br>";
}

将其保存到服务器根目录上的r.php,然后在.htaccess中进行一些测试,
例如,我要匹配不以语言前缀开头的网址

RewriteRule ^(?!(en|de)/)(.*)$ /r.php?$1&$2 [L] #$1&$2&...
RewriteRule ^(.*)$ /r.php?nomatch [L] #report nomatch and exit

1
就像我在O / P上的第4点中提到的那样,仅使用phpinfo()存根基本上是一样的。寻找QUERY_STRING
TerryE

0

正如@JCastell指出的那样,在线测试人员可以很好地测试针对.htaccess文件的单个重定向。但是,更有趣的是公开的api,可用于使用json对象批量测试网址列表。但是,为了使其更有用,我编写了一个小的bash脚本文件,该文件使用curljq提交了一个URL列表,并将json响应解析为CSV格式的输出,并在htaccess文件中匹配了行号和规则以及重定向的网址,因此比较电子表格中的网址列表并快速确定哪些规则不起作用非常方便。


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.