我的堆栈:
- 灯
- 阿帕奇/2.4.41
背景信息:
我最近为客户推出了一个新网站。在重新设计过程中,我们决定:
- 切换到全站 HTTPS
- 从 URL 中删除 .php 扩展名
- 切换到 CMS
旧 URL 示例:
http://www.example.com/courses/acme-course.php
新 URL 示例:
https://www.example.com/courses/acme-course
我的问题:
当用户导航到其中一个旧 URL 时,会发生不必要的额外 301 重定向。
我不明白为什么要创建额外的 301 重定向,而不是使用单个 301 重定向将用户直接发送到正确的目标 URL。
有趣的观察:
当我使用带有 HTTPS 而不是 HTTP 的旧 URL 时,不会发生不必要的额外 301 重定向。
例子:
https://www.example.com/courses/acme-course.php
_
使用上述 URL 将正确地执行单个 301 重定向到正确的目标 URL:https://www.example.com/courses/acme-course
以下是 301 重定向链的示例:
原始请求 URL:
http://www.example.com/courses/acme-course.php
第一次 301 重定向(不必要):
从:
http://www.example.com/courses/acme-course.php
到:
https://www.example.com/index.php?url=courses/acme-course.php
第二次 301 重定向(正确的最终目标网址):
从:
https://www.example.com/index.php?url=courses/acme-course.php
到:
https://www.example.com/courses/acme-course
我的.htaccess 代码:
# (1) General Settings
<IfModule mod_rewrite.c>
Options +FollowSymLinks
RewriteEngine On
</IfModule>
# (2) Force WWW
<IfModule mod_rewrite.c>
RewriteCond %{HTTPS} !=off
RewriteCond %{HTTP_HOST} !^www\. [NC]
RewriteCond %{SERVER_ADDR} !=127.0.0.1
RewriteCond %{SERVER_ADDR} !=::1
RewriteRule ^ %{ENV:PROTO}://www.%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
</IfModule>
# (3) Force HTTPS
<IfModule mod_rewrite.c>
RewriteCond %{HTTPS} !=on
RewriteRule ^(.*)$ https://%{HTTP_HOST}/$1 [R=301,L]
</IfModule>
# (4) URL Routing for CMS
<IfModule mod_rewrite.c>
RewriteCond %{HTTPS} =on
RewriteRule ^ - [env=proto:https]
RewriteCond %{HTTPS} !=on
RewriteRule ^ - [env=proto:http]
## Check if file/directory exists
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
## Route all other URLs to index.php/URL
RewriteRule ^(.*)$ index.php?url=$1 [PT,L,QSA]
</IfModule>
答案1
你有两个主要问题....
- 文件中的指令顺序错误。需要删除
.htaccess
HTTP 到 HTTPS 和规范重定向www
前您的前端控制器将 URL 路由到您的 CMS。因此,错误的外部重定向会/index.php?url=courses/acme-course.php
暴露您的内部 CMS URL 结构。
删除
.php
实际上不是由您的.htaccess
指令执行的?!我认为这一定是由您的应用程序/CMS 逻辑执行的?因此,这将总是导致第二次重定向(因为.htaccess
在同一 URL 路径上重定向到 HTTPS)。您需要在文件顶部执行类似下面的操作.htaccess
来删除.php
扩展名。RewriteRule (.+)\.php$ https://www.example.com/$1 [R=301,L]
更新:如果我重新排序规则/条件,我的选项 +FollowSymlinks 的位置是否保持不变?
这没什么关系在哪里指令Options
发生。但是,将其放在顶部附近是合乎逻辑的(从可读性的角度来看)。(Apache 指令不一定按照它们在配置文件中出现的顺序执行,因为每个模块都独立工作。)
假设您正在手动编码.htaccess
文件,那么它可以被整理......
不需要(多个)
<IfModule mod_rewrite.c>
包装器。mod_rewrite 是可选的吗?您的网站是否要移植到未启用 mod_rewrite 的多个服务器?无需多个
RewriteEngine
指令。最后的例如胜利并控制整个文件。多个
<IfModule>
块RewriteEngine
是典型的通过代码自动编辑和/或设计为在多个服务器上不经编辑即可运行的系统。
因此,您的.htaccess
文件应该按以下顺序重写:
Options +FollowSymlinks
# Enable the rewrite engine...
RewriteEngine On
# ----------------------------------------------------------------------
# | Forcing `https://` |
# ----------------------------------------------------------------------
# Redirect to HTTPS on the "same host" (requirement for HSTS)
RewriteCond %{HTTPS} !=on
RewriteRule (.*) https://%{HTTP_HOST}/$1 [R=301,L]
# ----------------------------------------------------------------------
# | Forcing `www` |
# ----------------------------------------------------------------------
RewriteCond %{HTTP_HOST} !^www\.
RewriteCond %{SERVER_ADDR} !=127.0.0.1
RewriteCond %{SERVER_ADDR} !=::1
RewriteRule ^ https://www.%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
# ----------------------------------------------------------------------
# | URL Routing for CMS |
# ----------------------------------------------------------------------
# (3)
RewriteCond %{HTTPS} =on
RewriteRule ^ - [env=proto:https]
RewriteCond %{HTTPS} !=on
RewriteRule ^ - [env=proto:http]
# (4) - Check if physical file exists
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
# (5) - Rewrite all other URLs to index.php/URL
RewriteRule (.*) index.php?url=$1 [L,QSA]
补充笔记:
环境
PROTO
变量包含所请求的任何协议。按照重定向的顺序,现在将始终是 HTTPS。这个变量的根本原因是,如果访问的是 HTTP,CMS 可以重定向到 HTTP;如果访问的是 HTTPS,CMS 可以重定向到 HTTPS。如果您强制使用 HTTPS,那么它实际上并不适用。(尽管您的应用程序可能仍会使用此环境变量。)您很少应该
NC
在否定条件下使用该标志。因此,我将其从条件中删除!^www\.
。您希望它在主机不以www.
- 全部小写开头时重定向。使用该NC
标志将无法重定向WwW.
- 尽管这种情况无论如何都非常罕见。我已经删除了 www 规范重定向上不必要的 HTTPS 检查。
PT
最后的标志在RewriteRule
中不是必需的.htaccess
。.htaccess
这是默认行为(传递)。您需要在测试之前清除浏览器缓存,因为错误的 301 重定向很可能已被浏览器缓存。因此,使用 302(临时)重定向进行测试是个好主意。