Search code examples
apache.htaccessmod-rewrite

.htaccess Wildcard subdirectory to single subdirectory


I'm looking to create a multi-tenant project with the address structure as follows

https://example.com/TENANTNAME/file.php
https://example.com/TENANTNAME/api/file.php
https://example.com/TENANTNAME/assets/js/main.js

And have it rewrite to a single subfolder, say /BASE/ - the idea being I can have a single set of files and then inspect the URL to figure out the rest.

Thank you

I did get it working to a point with the following, but further subfolders (Edited from subdomains) were not properly redirected, so the main index.php was fine but any anything in the assets folder was giving a server error

RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_URI} !^/BASE/(.*)/
RewriteRule ^(.*)/(.*)$ /BASE/$2 [L]

As an example, I would expect the following internal rewrites

https://example.com/TENANTNAME/index.php
to
https://example.com/BASE/index.php

https://example.com/TENANTNAME/assets/js/main.js
to
https://example.com/BASE/assets/js/main.js

The index.php example did work with my attempt however the assets example, with further subfolders, did not


Solution

  • 1. The test for existing files or directories

    My first question was "Could the wildcard folders exist on the disk?"

    If yes, then the first two rewrite conditions could potentially stop the rewrite rule to execute when a request matches an existing file or directory.

    So I think you can remove these conditions, as you probably don't need them.

    2. Testing the first part of the path

    The next rewrite condition, testing that it's not the /BASE/ root directory, should be sufficient. But it can be slightly optimized by replacing

    RewriteCond %{REQUEST_URI} !^/BASE/(.*)/
    

    By

    RewriteCond %{REQUEST_URI} !^/BASE/
    

    We don't need to capture what's behind the second slash. It actually creates a variable called %1 that can be used in the rewrite rule destination URL. But in any case, we have some other capturing groups in the rewrite rule pattern.

    3. Changing the rewrite rule pattern

    They might be a little problem of greediness in the regex of the rewrite rule. ^(.*)/ could be capturing to many characters, leading to capturing some other slashes. So there are two solutions to solve it:

    • Make it ungreedy by adding a question mark: ^(.*?)/
      By the way, I would replace the * by + because we want to match at least one char.

    • Instead of the dot, use "not a slash": ^([^/]+)/
      This has a big advantage because we know that a folder should not contain slash characters. The regex should run faster than the ungreedy version above and will also be safer.

    Also, I thought that your PHP scripts might like to know what was the original URL before the rewrites. You can get this information by reading the $_SERVER array but why not converting it to a query parameter as we captured it in any case? This can be achieved this way:

    RewriteRule ^([^/]+)/(.*)$ /BASE/$2?original_root=$1 [L,QSA]
    

    The first capturing group is $1 and can be added at the end of the URL, as a query parameter. By the way, the rewrite rule pattern is applied on the path without the query string (which is normally added back to the destination URL). But here, as I am creating a new query string, I have to enable the QSA option which will append the original query string to the newly created one. (QSA = query string append).

    This way, an URL like:

    /any-folder/something/script.php?id=6&op=view
    

    should become:

    /BASE/something/script.php?original_root=any-folder&id=6&op=view