Apache 2 重写规则错误地更改了 URL

Apache 2 rewrite rule is incorrectly changing the URL

注意这是我之前问过的问题的重复。然而它最初措辞糟糕并且被编辑了很多次它几乎变成了一个不同的问题,因此我决定提出一个新问题

文件

我有以下文件:

example.dev.conf 虚拟主机文件

这是一个非常简单的虚拟主机文件:

<VirtualHost *:80>
        ServerName example.dev
        ServerAlias *.example.dev
        DocumentRoot /var/www/example

        <Directory /var/www/example >
                AllowOverride All
                Order allow,deny
                Allow from all
        </Directory>
</VirtualHost>

<VirtualHost *:443>
        ServerName example.dev
        ServerAlias *.example.dev 
        DocumentRoot /var/www/example

        <Directory /var/www/example >
                AllowOverride All
                Order allow,deny
                Allow from all
        </Directory>
</VirtualHost>

.htaccess

以下文件将所有 URL 重定向到等效的 https,除非它以 /api/ 开头。

还应确保所有路由都使用 index.php 控制器。

RewriteEngine On

# Handle SSL
RewriteCond %{HTTPS} off
RewriteCond %{REQUEST_URI} !^/api/ [NC]
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R]

# Handle Front Controller...
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.php [L]

问题

这对大部分情况都有效。

例如 http://example.dev/blah 重定向到 https://example.dev/blah

但是,对于以 /api/.

开头的任何内容,它似乎没有按预期工作

例如:http://example.dev/api/blah 重定向到 https://example.dev/index.php,而它根本不应该进行重定向!

请注意,如果我删除 htaccess 文件的最后 3 行,重定向工作正常。

如何复制?

我发现有些人发现很难复制我的问题,并坚持认为某处有其他规则。因此,我决定尽我所能展示我的所有步骤,以便很容易复制:

vagrant init ubuntu/trusty64
vagrant up --provider virtualbox
vagrant ssh

sudo apt-get install apache2
sudo a2enmod rewrite

cd /etc/apache2/sites-available/
sudo touch example.dev.conf   
# copy contents of the example.dev.conf vhost file as seen above
sudo a2ensite example.dev.conf

cd /var/www/
sudo mkdir example
sudo chown -R www-data:www-data /var/www/example
sudo chmod 777 -R /var/www/example
cd example
echo "test" > index.php
touch .htaccess
# Copy contents of .htaccess file as seen above

sudo chown -R www-data:www-data /var/www/example
sudo chmod 777 -R /var/www/example

sudo service apache2 restart

很可能,/api/ 请求被重定向到 https://example.dev/index.php 的原因是以下步骤

  • /api/...重写为index.php
  • 重定向 index.phphttps://...

要了解为什么会发生这种情况,请查看 Ruleset Processing 的工作原理。按顺序尝试规则,但是当发生重写时(图中的 "URI changed?"),Apache 会再次使用生成的 URI 启动,直到不再发生重写。


要避免 /api/ 重定向到 https://...,您还必须排除第一个规则的 index.php,例如

RewriteCond %{HTTPS} off
RewriteCond %{REQUEST_URI} !^/api/ [NC]
RewriteCond %{REQUEST_URI} !^/index\.php$ [NC]
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R]

我想我已经解决了这个问题。

阅读此文后:http://httpd.apache.org/docs/current/rewrite/flags.html#flag_l 最后一个标志并不一定意味着没有使用进一步的规则。

例如,对于以下文件:

RewriteEngine On

# Handle SSL
RewriteCond %{HTTPS} off
RewriteCond %{REQUEST_URI} !^/api/ [NC]
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=308]

# Handle Front Controller...
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.php [L]
  • /api/blah 重写为 index.php
  • 然后它再次检查所有规则。根据文档,发生这种情况的原因可能如下:

It is possible that as the rewritten request is handled, the .htaccess file or section may be encountered again, and thus the ruleset may be run again from the start. Most commonly this will happen if one of the rules causes a redirect - either internal or external - causing the request process to start over.

  • index.php 然后匹配第一条规则,因此创建到 https://example.dev/index.php 的重定向。

解决方案是改用 END flag(如@anubhava 所述,仅在 Apache 2.4+ 中受支持),以防止重新处理规则。

所以解决方法如下:

RewriteEngine On

# Handle SSL
RewriteCond %{HTTPS} off
RewriteCond %{REQUEST_URI} !^/api/ [NC]
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R]

# Handle Front Controller...
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.php [END]

现在我去 http://example.dev/api/blah:

  • 改写为index.php
  • 因为有END规则,规则不再处理!
  • 因此 http://example.dev/api/blah 保持原样并根据需要使用 index.php 控制器。

感谢@Olaf Dietsche 让我走上正轨并建议 308 重定向!

其他两个答案都很好,但我 post 另一个答案是出于以下 3 个原因:

  1. :除了用户直接在浏览器中输入 http://domain.con/index.php 外,它适用于所有情况。根据要求,除了以 /api/ 开头的任何内容都应重定向到 `https://
  2. END 标志仅在 Apache 2.4+ 中受支持,因此此答案不适用于 Apache 2.2 上面临类似答案的人。
  3. POST 请求应使用 307/308 重定向,否则 post 数据将丢失(请参阅下面 Olaf 的有用评论)。

为了使其适用于这两种情况,我建议使用 THE_REQUEST 而不是 REQUEST_URI,因为 REQUEST_URI 在最后一条规则执行后变为 /index.php

RewriteEngine On

# Handle SSL
RewriteCond %{HTTPS} off
RewriteCond %{THE_REQUEST} !\s/+api/ [NC]
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=308,NE]

# Handle Front Controller...
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.php [L]