问题
Note this is a repeat of a previous question I have asked. However it was initially badly worded and was edited so many times it almost became a different question, hence I decided to make a new question
Files
I have the following files:
example.dev.conf vhost file
This is a very simple vhost file:
<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
The following file redirects all urls to the https equivalent, unless it starts with /api/
.
It should also makes sure that ALL routes uses the index.php
controller.
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]
The Problem
This works for the most part.
For example http://example.dev/blah
redirects to https://example.dev/blah
However, it does not seem to work as expected for anything that starts with /api/
.
For example: http://example.dev/api/blah
redirects to https://example.dev/index.php
, when it should have not done a redirect at all!
Note that the redirects work fine if I remove the final 3 lines of the htaccess file.
How to replicate?
I have found that some people found it difficult to replicate my problem and have insisted there is some other rule somewhere. Hence I decided to show ALL my steps the best I can so it's very easy to replicate:
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
回答1:
Both other answers are good but I am posting another for these 3 reasons:
- Answer from Olaf: It will work for all the cases except when user directly enters
http://domain.con/index.php
in the browser. As per requirements anything except when starting with/api/
should be redirected to `https:// - Answer from Yahya:
END
flag is only supported in Apache 2.4+ so this answer won't work for someone on Apache 2.2 facing similar answer. POST
requests should be redirected with307/308
otherwise post data will be lost (See Olaf's helpful comment below).
To make it work for both these cases I suggest using THE_REQUEST
instead of REQUEST_URI
because REQUEST_URI
changes to /index.php
after last rule execution.
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]
回答2:
Most likely, the reason for /api/
requests being redirected to https://example.dev/index.php
are the following steps
- rewrite
/api/...
toindex.php
- redirect
index.php
tohttps://...
To see why this happens, look at how Ruleset Processing works. The rules are tried in order, but when a rewrite occurs ("URI changed?" in the picture), Apache starts with the resulting URI again, until no further rewrite occurs.
To avoid the redirect to https://...
for /api/
, you must also exclude index.php
for the first rule, e.g.
RewriteCond %{HTTPS} off
RewriteCond %{REQUEST_URI} !^/api/ [NC]
RewriteCond %{REQUEST_URI} !^/index\.php$ [NC]
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R]
回答3:
I think I managed to figure out the issue.
Upon reading this: http://httpd.apache.org/docs/current/rewrite/flags.html#flag_l it turns out the last flag doesn't necessarily mean no further rules are used.
For example, for the following file:
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
is rewritten toindex.php
- It then goes through all the rules again. According to the documentation, this can happen for the following reasons:
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
then matches the first rule so a redirect tohttps://example.dev/index.php
is created.
The solution is to instead use the END flag (which as @anubhava mentioned, is only supported in Apache 2.4+), to prevent the rules being re-processed.
So the solution is as follows:
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]
Now when I go to http://example.dev/api/blah
:
- It is rewritten to
index.php
- As it has the
END
rule, the rules are not processed again! - Therefore
http://example.dev/api/blah
stays where it is and uses theindex.php
controller as desired.
Thanks @Olaf Dietsche for getting me to the right track and suggesting the 308 redirect!
来源:https://stackoverflow.com/questions/42695014/apache-2-rewrite-rule-is-incorrectly-changing-the-url