Home > Net >  How can I change my mod_rewrite rules to work in the parent directory?
How can I change my mod_rewrite rules to work in the parent directory?

Time:02-18

I have an MVC app with index.php located in a /public subfolder. Currently, I point Apache to the /public folder and use .htaccess in there to rewrite MVC requests to index.php.

Current Apache conf:

<VirtualHost>
  DocumentRoot /var/www/html/public
</VirtualHost>

Current .htaccess in /public:

RewriteEngine On

RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-l
RewriteRule ^(.*)$ index.php?$1 [L,QSA]

Target Apache conf:

<VirtualHost>
  DocumentRoot /var/www/html
</VirtualHost>

Target .htaccess, but I can't get both the MVC and asset URLs to rewrite correctly:

DirectoryIndex index.php

RewriteEngine On
RewriteBase /public

# If it's a file/directory/symlink, serve it.
RewriteCond %{REQUEST_FILENAME} -f [OR]
RewriteCond %{REQUEST_FILENAME} -d [OR]
RewriteCond %{REQUEST_FILENAME} -l
RewriteRule ^.*$ - [NC,L]

# Otherwise let's go MVC
RewriteRule ^.*$ index.php [NC,L]

Example URLs:

domain.com/login (MVC URL, rewritten to public/index.php)
domain.com/assets/styles.css (non-MVC URL, rewritten to public/assets folder)

CodePudding user response:

Unless you are setting up a local development environment to simulate a more restrictive live server environment then...

Not sure why you want to do this, as your current config (setting the DocumentRoot to /public) would seem to be the most desirable (and simple) setup.

Setting the DocumentRoot to the parent directory and rewriting to the /public subdirectory instead is often a workaround when you are unable to change the DocumentRoot. This is ultimately more work and exposes the /public subdirectory, which now has to be blocked/redirected to prevent direct access.

Anyway, moving to a /public subdirectory in the public HTML space...

Having set the DocumentRoot to /var/www/html there are two ways to accomplish this, with either 1 or 2 .htaccess files. (Although, this also raises another question... why are you using .htaccess when you have access to the server config? Although there can be perfectly valid reasons for this.)

Method#1 - Two .htaccess files

It is arguably easier to do this with two .htaccess files. One in the document root that rewrites all requests to the /public subdirectory and one in the /public subdirectory that rewrites requests to your MVC app, in much the same way you are doing already. The presence of the /public/.htaccess file also serves to prevent a rewrite loop.

For example:

# (1) /.htaccess

RewriteEngine On

# (OPTIONAL) If you need to allow access to files/directories/symlinks
#            outside of the "public" directory
#RewriteCond %{REQUEST_FILENAME} -f [OR]
#RewriteCond %{REQUEST_FILENAME} -d [OR]
#RewriteCond %{REQUEST_FILENAME} -l
#RewriteRule ^ - [L]

# Rewrite all requests to the "/public" subdirectory
RewriteRule (.*) public/$1 [L]
# (2) /public/.htaccess

RewriteEngine On

# If "public" directory is requested directly then redirect to remove it
RewriteCond %{ENV:REDIRECT_STATUS} ^$
RewriteRule (.*) /$1 [R=301,L]

# Stop early if already rewritten to MVC
RewriteRule ^index\.php$ - [L]

# Rewrite to MVC app, otherwise serve assets
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-l
RewriteRule (.*) index.php?$1 [QSA,L]

Method#2 - One .htaccess file in the document root

Alternatively, you have just one .htaccess file in the document root.

For example:

# /.htaccess (only)

RewriteEngine On

# If "public" directory is requested directly then redirect to remove it
RewriteCond %{ENV:REDIRECT_STATUS} ^$
RewriteRule ^public(?:/(.*))?$ /$1 [R=301,L]

# Stop early if already rewritten to "public" directory
RewriteRule ^public/ - [L]

# If it's a file/directory/symlink, serve it.
RewriteCond %{REQUEST_FILENAME} -f [OR]
RewriteCond %{REQUEST_FILENAME} -d [OR]
RewriteCond %{REQUEST_FILENAME} -l
RewriteRule ^ - [L]

# Rewrite to assets in "public" directory if they exist
RewriteCond %{REQUEST_URI} \.\w{2,4}$
RewriteCond %{DOCUMENT_ROOT}/public%{REQUEST_URI} -f
RewriteRule !^public/ public%{REQUEST_URI} [L]

# Otherwise rewrite request to the MVC app in "public" directory
RewriteRule (.*) public/index.php?$1 [QSA,L]

I'm assuming all your "assets" have file extensions of between 2 and 4 characters. An optimisation to the above is if all your "assets" are referenced by /assets/... then you could simply rewrite all these requests to /public/assets/... - no need for filesystem checks. For example:

# Rewrite to assets in "public" directory (unconditionally)
RewriteRule ^assets/ public%{REQUEST_URI} [L]

Although this would mean you cannot have routes in the MVC app that start /assets/. But you probably shouldn't anyway.

Aside: If you aren't serving directories directly (it's unusual to do so these days) then you don't need to check whether the request maps to a directory in the rewrite rules. Just allow it to pass to your MVC app and trigger a 404 (rather than a 403) - which could be preferable.

  • Related