我编写了一个小网站(4 页,仅 HTML),我想通过在我的 .htaccess 文件中添加一些重写规则来从 URL 中删除 .html 扩展名,我在 Google 上搜索了一下,发现了几个类似于这样的代码片段:
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME}\.html -f
RewriteRule ^(.*)$ $1.html
</IfModule>
以下两个 URL 都提供相同的内容(这是我所期望的)
https://example.io/contact
https://example.io/contact.html
但是下面会出现 500 错误:
https://example.io/contact/
此目录不存在,如果我删除上面提到的重写代码,它将返回 404,这正是我所期望的。为什么上面的代码会导致 500 错误?
更有趣的是,这将500:
https://example.io/contact/blah
但是这会 404:
https://example.io/contact123/blah
contact/ 和 contact123/ 都不存在作为目录,但是 contact.html 存在,而 contact123.html 不存在。
任何帮助或解释都将不胜感激。
编辑:
MrWhite 已经给出了正确答案,但对于任何将来查看的人来说,Apache 错误日志如下所示:
[Thu Oct 24 20:49:47.722210 2019] [core:error] [pid 13001:tid 139915446667008] [client 1.2.3.4:39006] AH00124: Request exceeded the limit of 10 internal redirects due to probable configuration error. Use 'LimitInternalRecursion' to increase the limit if necessary. Use 'LogLevel debug' to get a backtrace.
我检查了日志但不确定为什么会发生这种情况,但忘了将其包含在问题中。
答案1
总结/contact/
对(或)的请求/contact/blah
会导致重写循环(500 内部服务器错误响应),因为REQUEST_FILENAME
包含映射的文件系统路径;而不是您期望的 URL 路径。
RewriteCond %{REQUEST_FILENAME} !-d RewriteCond %{REQUEST_FILENAME}\.html -f RewriteRule ^(.*)$ $1.html
REQUEST_FILENAME
“问题”在于第二种情况的使用。REQUEST_FILENAME
服务器变量包含绝对文件系统路径后URL 已映射到文件系统。这不一定与 URL 路径相同 - 但此条件假设就是这样。当 URL 路径包含未映射到文件系统的整个路径段(如/contact/blah
或/contact123/blah
)时,REQUEST_FILENAME
本质上会“简化”为映射到目录的最后一个路径段,加上“文件名”(即.../contact
和.../contact123
分别 - 文档根目录,即/
,是本例中最后一个匹配的目录)。
要求/contact
当您请求时/contact
,URL 路径为/contact
并且REQUEST_FILENAME
为/path/to/document-root/contact
- 因此REQUEST_FILENAME
直接映射到 URL 路径。测试条件/path/to/document-root/contact.html
成功,请求被重写为contact.html
。一切都很好。
请求/contact/
或/contact/blah
但是,当您请求时/contact/
,URL 路径为/contact/
,但REQUEST_FILENAME
又是/path/to/document-root/contact
(没有斜杠后缀)。测试条件再次成功(如上所示),但请求被重写为contact/.html
(因为.html
附加到被捕获URL 路径,即$1.html
)。处理循环,REQUEST_FILENAME
评估结果与之前相同(条件再次成功),并且请求第二次重写为contact/.html.html
。等等,导致重写循环最终达到内部限制(默认为 10),此时它“中断”,服务器将以 500 内部服务器错误进行响应。
要求/contact123/blah
/contact123/blah
另一方面,会导致 404,因为REQUEST_FILENAME
服务器变量变为/path/to/document-root/contact123
不/path/to/document-root/contact123.html
存在,所以首先不会发生重写。
解决方案
为了“修复”此行为,我们需要确保我们正在测试最终要重写的相同文件/ URL 路径。
DOCUMENT_ROOT
我们可以通过连接和REQUEST_URI
服务器变量(或反向引用)来构造绝对文件名(进行测试)$1
,其中包含根相对 URL 路径。(请注意,REQUEST_URI
包括斜杠前缀,而$1
反向引用不包括。)
例如:
# Rewrite request to append ".html" extension to URL
RewriteCond %{DOCUMENT_ROOT}/$1.html -f
RewriteRule (.+) $1.html [L]
现在,测试条件正在测试请求将被重写到的相同文件系统路径(如果成功)。
无需检查请求是否映射到目录和它确实会映射到文件(附加.html
扩展名时),除非您还有与文件基名同名的目录(例如basename.html
和basename/
)。但如果是这种情况,那么其中一个或另一个无论如何都不会无法访问,因此最好避免这种情况。
/contact/
对、/contact/blah
或全部 的请求/contact123/blah
现在将按预期产生 404 的结果。
请注意,无需使用反斜杠转义文字点RewriteCond
测试字符串因为这不是一个正则表达式。
次要观点... (and ) 上的^
and$
锚点是不必要的,因为(and ) 量词默认是贪婪的(尽管有些用户似乎仍然喜欢它们,因为^(.*)$
^(.+)$
*
+
可读性?)。您还应该在 上包含L
( ) 标志。如果这是文件中唯一(或最后)的规则,则这不是必需的,但如果您以后要添加更多规则,则可能必须这样做(并且必须记住以这种方式修改现有规则很容易出错)。last
RewriteRule
.htaccess
$1
通过在指令中使用反向引用RewriteCond
,这确实假设.htaccess
文件位于文档根目录中,否则,写入的文件系统检查将不正确。如果文件.htaccess
位于子目录中,则将指令更改RewriteCond
为使用REQUEST_URI
服务器变量。例如:
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI}.html -f
RewriteRule (.+) $1.html [L]
优化
通过将正则表达式限制为不包含类似文件扩展名的 URL,您可以避免不必要地检查所有已包含文件扩展名的请求(即所有静态资源)。例如:
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI}.html -f
RewriteRule !\.\w{2,4}$ %{REQUEST_URI}.html [L]