it seems that accessing site.com/api/v1/ does not execute api_v1.php. Instead it would continue and execute index.php
Yes, because the first rule rewrites the request to api_v1.php
and this ends up getting rewritten to index.php
by the second rule (because php
is not in the list of excluded extensions in the first condition, the second condition is also successful so the third condition is not processed - see below) during the second pass of the rewrite engine. In a directory context (ie. .htaccess
) the L
flag does not stop all processing, it just stops the current pass through the rewrite engine. The rewrite engine loops until the URL passes through unchanged.
You could resolve this on Apache 2.4 by using the END
flag, instead of L
, on the first rule to stop all processing by the rewrite engine.
For example:
RewriteRule ^api/v1/ api_v1.php [NC,END]
(I removed the trailing (.*)$
on the regex since it's not necessary here and makes the regex marginally less efficient.)
However, the fact that a request for an existing .php
file is rewritten by your later rules really demonstrates that your second and third rules do not seem to be working as intended...
#2. Rewrite to index.php except for for design/document/favicon/robots files that exists
RewriteCond %{REQUEST_URI} !.(css|js|png|jpg|jpeg|bmp|gif|ttf|eot|svg|woff|woff2|ico|webp|pdf)$
RewriteCond %{REQUEST_URI} !^(robots\.txt|favicon\.ico)$ [OR]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ index.php [L]
#3. Rewrite enything else
RewriteRule ^(.*)$ index.php [L]
If you request something.php
then the first condition is successful (because .php
is not included in the list). The second condition is also successful (it's not robots.txt
or favicon.ico
). Since the second condition is explicitly OR'd with the third condition, the third condition is not processed. All the conditions are successful and the request is rewritten to index.php
(regardless of whether something.php
exists or not).
If, on the other hand, you request resource.css
then the first condition fails (because it has a .css
extension). Since this condition is implicitly AND'd, no further conditions are processed and the rule is not triggered. The third rule then unconditionally rewrites resource.css
to index.php
, regardless of whether it exists or not!
So, this doesn't check that a requested resource (css
, jpg
, etc.) "exists" as would seem to imply by the preceding comment. The third condition (that checks the request does not map to a file) is only processed when the preceding OR'd condition fails AND the first condition is successful. In other words, the 3rd condition is only processed when you request /robots.txt
- which I'm sure is not your intention.
In other words, rules #2 and #3 (despite their apparent complexity) would seem to be rewriting pretty much everything to index.php
.
Complete solution
Based on your additional comments:
For a subset of file types, serve the resource - if it exists. If the resource does not exist then send the request to index.php
.
For other file types, send the request to index.php
, regardless of whether that resource exists or not.
A few requests (ie. /robots.txt
and /favicon.ico
) should not be sent to index.php
.
Try the following instead:
# Prevent 301 redirect with slash when folder exists and does not have slash appended
# This is not a security issue here since a PHP router is used and all the paths are redirected
DirectorySlash Off
# Since "DirectorySlash Off" is set, ensure that mod_auotindex directory listings are disabled
Options -Indexes
RewriteEngine On
#1. Rewrite for API url
RewriteRule ^api/v1/ api_v1.php [NC,END]
#2. Known URLs/files are served directly
RewriteRule ^(index\.php|robots\.txt|favicon\.ico)$ - [END]
#3. Certain file types (resources) are served directly if they exist
RewriteCond %{REQUEST_FILENAME} -f
RewriteRule \.(css|js|png|jpe?g|bmp|gif|ttf|eot|svg|woff|woff2|ico|webp|pdf)$ - [END]
#4. Rewrite everything else
RewriteRule ^ index.php [END]
The rules finish early for anything that needs to be served directly, so we only need to rewrite to index.php
once in the last rule.
Note that I've included index.php
in rule #2. This is an optimisation, although not strictly necessary if using the END
flag (as opposed to L
) on the last rule.
Optionally, you might choose to redirect any direct requests to index.php
back to the root (to canonicalise the URL for search engines). This is just in case /index.php
gets exposed (or guessed) to the end user.
For example, add the following after the first rule for the API:
#1.5 Redirect direct requests to "/index.php" back to "/" (root)
RewriteCond %{ENV:REDIRECT_STATUS} ^$
RewriteRule ^index\.php$ / [R=301,L]
The condition that checks against the REDIRECT_STATUS
environment variable is necessary to prevent a redirect loop - to avoid redirecting the rewritten URL. (Although, again, this is not strictly required if using the END
flag on the last/rewrite rule.)