如何使RewriteRule(.htaccess)中的[L]标志真正起作用?

5
作为新手,我试图全面描述我的问题并表达我的问题时,产生了大量的文本。如果您不想阅读整个内容,请查看“附加观察”部分中关于(即“证明”)[L]标志无法工作的误解。我误解明显行为的原因以及给定问题的解决方案均在我的回答中描述。

设置


我在我的.htaccess文件中有以下代码:

# disallow directory indexing
Options -Indexes

# turn mod_rewrite on
Options +FollowSymlinks
RewriteEngine on

# allow access to robots file
RewriteRule ^robots.txt$ robots.txt [NC,L]

# mangle core request handler address
RewriteRule ^core/(\?.+)?$ core/handleCoreRequest.php$1 [NC,L]

# mangle web file adresses (move them to application root folder)
# application root folder serves as application GUI address
RewriteRule ^$ web/index.html [L]
# allow access to images
RewriteRule ^(images/.+\.(ico|png|bmp|jpg|gif))$ web/$1 [NC,L]
# allow access to stylesheets
RewriteRule ^(css/.+\.css)$ web/$1 [NC,L]
# allow access to javascript
RewriteRule ^(js/.+\.js)$ web/$1 [NC,L]
# allow access to library scripts, styles and images
RewriteRule ^(lib/js/.+\.js)$ web/$1 [NC,L]
RewriteRule ^(lib/css/.+\.css)$ web/$1 [NC,L]
RewriteRule ^(lib/(.+/)?images/.+\.(ico|png|bmp|jpg|gif))$ web/$1 [NC,L]

# redirect all other requests to application address
# RewriteRule ^(.*)$ /foo/ [R]

我的Web应用程序(以及其.htaccess文件)位于DOCUMENT_ROOTfoo子文件夹中(通过浏览器访问为http://localhost/foo/)。它的PHP核心部分位于foo/core,JavaScript GUI部分位于foo/web。正如上面的代码所示,我只想允许访问处理来自GUI的所有请求的单个核心脚本以及“安全”的Web文件,并将所有其他请求重定向到基本应用程序地址(最后一个注释的指令)。

问题


行为

它可以工作,直到我尝试取消注释最后一个重定向指令。 如果我注释掉更多行,则相应的页面部分停止工作等。

但是,当我取消注释最后一行时,这应该仅在所有先前规则匹配失败时执行(至少我是这样理解的),页面会进入重定向循环(Firefox会抛出错误页面,显示类似“此页面无法正确重定向”),因为它正在一遍又一遍地重定向到http://localhost/foo/,而且永远不会停止。

问题

我不明白的是这个规则的处理方式:

RewriteRule ^$ web/index.html[L]

特别是[L]标志。这个标志明显对我不起作用。 当最后一行被注释时,它可以正确地重定向,但是当我取消注释时,它总是被处理,尽管重写应该在[L]标志上停止。有人有什么想法吗?

此外,顺便说一下,我很高兴知道为什么我接下来尝试解决它的方法也不起作用:

RewriteEngine on
RewriteRule ^core/(\?.+)?$ core/handleCoreRequest.php$1 [NC,L]
RewriteRule ^(.*)$ web/$1 [L]
RewriteRule ^.*$ /foo/ [L]

这实际上根本不起作用。即使我删除最后一行,它仍然无法正确重定向任何内容。如果第一个示例中的重定向不起作用,那么重定向是如何工作的呢?
如果有人知道任何调试这些指令的方法,对我来说也会非常有益。我花了几个小时甚至没有最轻微的线索可以解决问题。

其他观察


在尝试bbadour给出的建议之后(并不是我以前没有尝试过,但现在我有了第二个意见,所以我又试了一次),它仍然没有起作用,我得出了以下观察结果。通过将最后一行重写为以下内容:
RewriteRule ^(.*)$ /foo/?uri=$1 [R,L]

或者这个

RewriteRule ^(.*)$ /foo/?uri=%{REQUEST_URI} [R,L]

通过使用Firebug的网络面板,我发现了更多证据,即[L]标志在先前提到的RewriteRule ^$ web/index.html [L]规则(让我们称其为“THE RULE”)中明显未按预期工作。在第一种情况下,我得到了[...]uri=web/index.html,在第二种情况下,我得到了[...]uri=/foo/web/index.html。这意味着THE RULE被执行了(将^$重写为web/index.html),但重写并没有停止。还有更多的想法,请吗?

3个回答

12

经过数小时的搜索和测试,我终于找到了真正的问题和解决方案。希望这也能帮助其他人,当他们遇到同样的问题时。

出现问题的原因


.htaccess文件在每次重定向后都被处理(即使没有[R]标志),

这意味着在处理RewriteRule ^$ web/index.html [L]后,mod_rewrite会正确停止重写,并转到文件末尾,然后正确重定向到/foo/web/index.html然后服务器开始处理新位置的.htaccess文件,该文件与之前相同。此时只有最后一个重写规则匹配并将其重新定向回/foo/(这次使用了[R],因此可以在浏览器中观察到重定向)......然后再次处理.htaccess文件,如此循环......

再明确一次:由于只有硬重定向才能被观察到,因此似乎忽略了[L]标志,但实际上不是这样。相反,.htaccess文件将被处理两次,来回重定向至/foo//foo/web/index.html之间。


解决方案


禁止直接访问子文件夹

要将子目录虚拟移动到应用程序根目录,必须使用额外的复杂条件重写。变量THE_REQUEST对于区分硬重定向和软重定向非常有用:

RewriteCond %{THE_REQUEST} ^GET\ /foo/web/
RewriteRule ^web/(.*) /foo/$1 [L,R]

为了匹配这个重写规则,必须满足两个条件。首先,在第二行中,“本地URI”必须以web/开头(对应于绝对Web URI /foo/web/)。其次,在第一行中,真实的请求URI也必须以/foo/web/开头。这意味着,仅当直接从浏览器请求web/子文件夹内的文件时,才会匹配规则,此时我们希望进行硬重定向。

从根目录重定向到子文件夹中的允许内容(软)

RewriteCond $1 !^web/
RewriteCond $1 ^(.+\.(html|css|js|ico|png|bmp|jpg|gif))?$
RewriteRule ^(.*)$ web/$1 [L,NC]

如果我们还没有重定向到允许的内容,那么我们希望只重定向到允许的内容,这就是第一个条件。第二个条件指定了允许内容的掩码。与此匹配的任何内容都将被软重定向,如果内容不存在,则可能返回404错误。

隐藏不在子文件夹或未允许的所有内容

RewriteRule !^web/ /foo/ [L,R]

对于所有不以web/开头的URI,这将进行硬重定向到应用程序根目录(请记住,在此时只有以web/开头的请求是允许内容的内部重定向)。


真实示例


在使用上面提到的解决方案提示后,我的代码逐渐转变为以下形式:

# disallow directory indexing
Options -Indexes

# turn mod_rewrite on
Options +FollowSymlinks
RewriteEngine on

# allow access to robots file
RewriteRule ^robots.txt$ - [NC,L]

# mangle core request handler address
# disallow direct access to core request handler
RewriteCond %{THE_REQUEST} !^(GET|POST)\ /asm/core/handleCoreRequest.php
RewriteRule ^core/handleCoreRequest.php$ - [L]
# allow access to request handler under alias
RewriteRule ^core/$ core/handleCoreRequest.php [NC,QSA,L]

# mangle GUI files adressing (move to application root folder)
# disallow direct access to GUI subfolder
RewriteCond %{THE_REQUEST} ^GET\ /foo/web/
RewriteRule ^web/(.*) /foo/$1 [L,R]
# allow access only to correct filetypes in appropriate locations
RewriteCond $1 ^$ [OR]
RewriteCond $1 ^(images/.+\.(ico|png|bmp|jpg|gif))$ [OR]
RewriteCond $1 ^(css/.+\.css)$ [OR]
RewriteCond $1 ^(js/.+\.js)$ [OR]
RewriteCond $1 ^(lib/js/.+\.js)$ [OR]
RewriteCond $1 ^(lib/css/.+\.css)$ [OR]
RewriteCond $1 ^(lib/(.+/)?images/.+\.(ico|png|bmp|jpg|gif))$
RewriteRule ^(.*)$ web/$1 [L,NC]

# hide all files not in GUI subfolder that are not whitelisted above
RewriteRule !^web/ /foo/ [L,R]


我不喜欢这种方法的原因是应用程序根目录必须在 .htaccess 文件中硬编码(据我所知),因此该文件必须在应用程序安装时生成,而不能仅仅复制。


谢谢你的观察。不管怎样,为什么软重定向会再次处理htaccess呢?我已经很生气了。这完全没有意义。 - Qwerty

0

尝试使用:

RewriteRule ^(.*)$ /foo/ [R,L]

如果它仍然循环,请在其前面放置一个RewriteCond,以便在已经是/foo/的情况下跳过规则。

尝试过了,但没有成功。我编辑了原帖以展示我发现的内容。 - hon2a

0

调试时,请尝试简化您的正则表达式和所请求的URL(要匹配的完整URL的一部分),并查看它是否正常工作。现在,逐步添加更多的正则表达式和测试URL的位,直到找到停止正常工作的地方。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接