Home > Net >  Apache RewriteCond %{REQUEST_FILENAME} !-f failing when file exists
Apache RewriteCond %{REQUEST_FILENAME} !-f failing when file exists

Time:11-10

I'm pretty sure the issue is that %{REQUEST_FILENAME} does not reference a URI as it as been changed, but instead as it has been requested.

I have some code like this:

<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule app/?(.*)$ /some-site/map-app/$1 [NC,QSA]
</IfModule>

<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule some-site/map-app/?(.*)$ /some-site/map-app/index.html [NC,L,QSA]
</IfModule>

The effect is supposed to be that

  1. /app goes to -> /some-site/map-app when searching for files
  2. if that fails (as it will often because it's an SPA), it goes to /some-site/map-app/index.html

For some reason it's rewriting every path to the index.html fallback. This means that #1 is occurring enough to meet the RewriteRule condition, but for some reason the RewriteCond are not working.

If I remove the logic for #2, the files resolve fine so the issue is not that the paths it produces are bad.

I've read the docs on "RewriteCond Specials" (https://httpd.apache.org/docs/2.4/mod/mod_rewrite.html)

Why are these two not failing for paths that exist -- and have been produced by the first logic block -- within the second logic block?

RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d

Some examples and the desired outcome:

  • /app/ -> /some-site/map-app/index.html
  • /app/map/id-3 -> /some-site/map-app/index.html
  • /app/app.js -> /some-site/map-app/app.js
  • /app/assets/img/1.png -> /some-site/assets/img/1.png (RewriteEngine logic not included in post, but example included in case it changes potential answers)

CodePudding user response:

Immediately after the first rewrite, the REQUEST_FILENAME server variable contains the rewritten URL-path only (eg. /some-site/map-app/foo), not the absolute filesystem path that the rewritten URL would map to. So, attempting to do a filesystem check on REQUEST_FILENAME at this stage will always fail.

The request needs to be remapped back to the filesystem for the REQUEST_FILENAME variable to be updated to the absolute filesystem path. This only occurs at the start of (and before each pass through) the rewriting engine.

You can force the rewriting engine to start over by simply including the L (last) flag on the first rule. This ends the current round of processing and passes the rewritten URL back into the rewrite engine, at which point the rewritten URL is remapped to the filesystem and REQUEST_FILENAME is updated.

Alternatively, don't use REQUEST_FILENAME in the second rule and instead manually construct the absolute filename from the DOCUMENT_ROOT and rewritten URL-path.

For example:

# If the previously rewritten URL does not map to a file (or directory)...
RewriteCond %{DOCUMENT_ROOT}/$0 !-f
RewriteCond %{DOCUMENT_ROOT}/$0 !-d
RewriteRule ^some-site/map-app(?:$|/(.*)) /some-site/map-app/index.html [NC,L]

Where $0 is a backreference to the entire URL-path that the RewriteRule pattern would match.

There is an issue with your existing regex (RewriteRule pattern). The regex app/?(.*)$ matches appanything since the slash is optional, which I'm sure is not the intention. Presumably you want to match app or app/ or app/<something>? This should also be anchored to the start of the URL-path, otherwise, it will also match /some-site/map-app/<something> (which is intended for the second rule). The same applies to the second rule (updated above).

So, try the following instead (if not using the L flag on the first rule):

RewriteEngine On

RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^app(?:$|/(.*)) /some-site/map-app/$1 [NC]

# If the previously rewritten URL does not map to a file (or directory)...
RewriteCond %{DOCUMENT_ROOT}/$0 !-f
RewriteCond %{DOCUMENT_ROOT}/$0 !-d
RewriteRule ^some-site/map-app(?:$|/(.*)) /some-site/map-app/index.html [NC,L]

No need for the <IfModule> wrapper or repeating the RewriteEngine directive. The QSA (Query String Append) flags were also superfluous, since this is the default action. I would also be wary of using the NC flag (on an internal rewrite) since this permits both /app and /APP to map to the same URL (minor duplicate content issue).

I also wonder whether the directory checks are really necessary?

  • Related