如果您请求 /article 和 /article 作为物理目录存在,则 Apache 的 mod_dir 将(默认情况下)附加尾部斜杠以“修复”URL。这是通过 301 永久重定向实现的 - 因此它将被浏览器缓存。
虽然物理目录与文件具有相同的基本名称并使用无扩展名 URL 会产生歧义。例如。 /article 应该访问目录/article/ 还是文件/article.html。无论如何,您似乎不想允许直接访问目录,所以这似乎可以解决这种歧义。
为了防止 Apache mod_dir 将尾部斜杠附加到目录,我们需要禁用 DirectorySlash。例如:
DirectorySlash Off
但如前所述,如果您之前访问过 /article,则重定向到 /article/ 将被浏览器缓存 - 因此您需要清除浏览器缓存才能生效。
由于您要删除文件扩展名,因此您还需要确保禁用 MultiViews,否则,mod_negotiation 将为基础文件发出内部子请求,并可能与 mod_rewrite 冲突。 MultiViews 默认情况下是禁用的,尽管某些共享主机出于某种原因确实启用了它。从您得到的输出来看,MultiViews 似乎没有启用,但最好确定...
# Ensure that MutliViews is disabled
Options -MultiViews
但是,如果您需要能够访问目录本身,则需要手动附加尾部斜杠并进行内部重写。虽然这似乎不是这里的要求。但是,您应该确保禁用目录列表:
# Disable directory listings
Options -Indexes
尝试访问任何目录(最终不会映射到文件 - 见下文)并且不包含 DirectoryIndex 文档将返回 403 Forbidden 响应,而不是目录列表。
请注意,跟随domain/article 的链接、刷新页面和手动输入domain/article 之间可能发生的唯一区别是缓存... 浏览器或任何中间代理缓存. (除非你有拦截锚点点击事件的 JavaScript?!)
您仍然需要将请求从/foo 重写为/foo.html 或/foo 到/foo/index.html(见下文),具体取决于您如何配置您的站点。尽管您最好选择其中一个,而不是两者都选择(您似乎暗示可能是这种情况)。
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME}\.html -f
RewriteRule ^(.*)$ $1.html
目前尚不清楚这对您来说是如何“工作”的 - 除非您看到缓存的响应?当您请求/article 时,第一个条件失败,因为它作为物理目录存在并且不处理规则。即使启用了 MultiViews,mod_dir 也会优先考虑并附加尾部斜杠。
检查.html 文件是否存在的第二个条件不一定是检查要重写的同一个文件。例如。如果您请求/foo/bar,其中/foo.html 存在,但没有物理目录/foo,那么RewriteCond 指令会检查/foo.html 的存在——这是成功的,但请求在内部被重写为@987654349 @(来自捕获的RewriteRule pattern) - 这会导致内部重写循环和 500 错误响应返回给客户端。请参阅 my answer 到 following ServerFault question,其中详细介绍了此处实际发生的情况。
如果我们假设任何包含看起来像文件扩展名的 URL(例如,您的静态资源 .css、.js 和图像文件)应该被忽略,我们还可以进行进一步优化,否则我们正在执行文件系统检查每个请求,这相对昂贵。
因此,为了将/article 形式的请求映射(内部重写)到/article.html 和/article/somearticle 到/article/somearticle.html,您需要修改上述规则以读取如下内容:
# Rewrite /foo to /foo.html if it exists
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI}.html -f
RewriteRule !\.\w{2,4}$ %{REQUEST_URI}.html [L]
RewriteCond TestString 中的文字点不需要用反斜杠转义 - 点在这里没有特殊含义;这不是正则表达式。
然后,要处理应映射到 /foo/index.html 的 /foo 形式的请求,您可以执行以下操作:
# Rewrite /foo to /foo/index.html if it exists
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI}/index.html -f
RewriteRule !\.\w{2,4}$ %{REQUEST_URI}/index.html [L]
通常,您会允许 mod_dir 提供 DirectoryIndex(例如 index.html),但在目录中省略了尾部斜杠,这可能会出现问题。
总结
综合以上几点,我们有:
# Disable directory indexes and MultiViews
Options -Indexes -MultiViews
# Prevent mod_dir appending a slash to directory requests
DirectorySlash Off
RewriteEngine On
# Rewrite /foo to /foo.html if it exists
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI}.html -f
RewriteRule !\.\w{2,4}$ %{REQUEST_URI}.html [L]
# Otherwise, rewrite /foo to /foo/index.html if it exists
RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI}/index.html -f
RewriteRule !\.\w{2,4}$ %{REQUEST_URI}/index.html [L]
这可以进一步优化,具体取决于您的站点结构以及您是否向.htaccess 文件添加更多指令。例如:
- 您可以在文件顶部检查请求的 URL 上的文件扩展名,以防止任何进一步的处理。然后可以“简化”每个后续规则中的
RewriteRule 正则表达式。
- 可能会阻止或重定向包含尾部斜杠的请求(以删除尾部斜杠)。
- 如果请求是针对
.html 文件,则重定向到无扩展名 URL。如果您同时处理/foo.html 和/foo/index.html,这会稍微复杂一些。但这只有在您更改现有的 URL 结构时才真正需要。
例如,实现上面的#1 和#2,可以将指令写成这样:
# Disable directory indexes and MultiViews
Options -Indexes -MultiViews
# Prevent mod_dir appending a slash to directory requests
DirectorySlash Off
RewriteEngine On
# Prevent any further processing if the URL already ends with a file extension
RewriteRule \.\w{2.4}$ - [L]
# Redirect any requests to remove a trailing slash
RewriteRule (.*)/$ /$1 [R=301,L]
# Rewrite /foo to /foo.html if it exists
RewriteCond %{DOCUMENT_ROOT}/$1.html -f
RewriteRule (.*) $1.html [L]
# Otherwise, rewrite /foo to /foo/index.html if it exists
RewriteCond %{DOCUMENT_ROOT}/$1/index.html -f
RewriteRule (.*) $1/index.html [L]
在更改为 301(永久)重定向之前始终使用 302(临时)重定向进行测试,以避免缓存问题。