解决方案

解决方案

我编写了一个小网站(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.htmlbasename/)。但如果是这种情况,那么其中一个或另一个无论如何都不会无法访问,因此最好避免这种情况。

/contact/对、/contact/blah或全部 的请求/contact123/blah现在将按预期产生 404 的结果。

请注意,无需使用反斜杠转义文字点RewriteCond 测试字符串因为这不是一个正则表达式。

次要观点... (and ) 上的^and$锚点是不必要的,因为(and ) 量词默认是贪婪的(尽管有些用户似乎仍然喜欢它们,因为^(.*)$^(.+)$*+可读性?)。您还应该在 上包含L( ) 标志。如果这是文件中唯一(或最后)的规则,则这不是必需的,但如果您以后要添加更多规则,则可能必须这样做(并且必须记住以这种方式修改现有规则很容易出错)。lastRewriteRule.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]

相关内容