madbean

Serving one Apache site from two parallel directories

23 May 2007

Some of you may say "whytf umofo", and some may say "I've always wanted to be able to do that!". This hack is for the later, but I will try below to explain to the former why it is useful. If anyone knows of an easier way to do it, please let me know.

Apache httpd serves static content out of its DocumentRoot :

DocumentRoot /usr/local/apache/htdocs
<Directory /usr/local/apache/htdocs>
  Order allow,deny
  Allow from all
</Directory>

The contents of that location in the filesystem might look like:

/usr/local/apache/htdocs/index.php
/usr/local/apache/htdocs/images/a.png
/usr/local/apache/htdocs/images/b.png
/usr/local/apache/htdocs/images/c.png

And to a web browser, the site looks like:

/index.php
/images/a.png
/images/b.png
/images/c.png

All very vanilla. But sometimes you want to be able to store the site in two parallel and overlapping directories in the filesystem, without changing the way it is presented to the browser.

/usr/local/apache/htdocs/index.php
/usr/local/apache/htdocs/images/a.png
/home/matt/alt_htdocs/images/b.png
/home/matt/alt_htdocs/images/c.png

If you don't have parallel or overlapping requirements, then Alias is your friend. If you do, read on.

The Hack

Do this:

DocumentRoot /usr/local/apache/htdocs
<Directory /usr/local/apache/htdocs>
  Order allow,deny
  Allow from all
</Directory>

RewriteEngine on

RewriteCond "/home/matt/alt_htdocs%{REQUEST_URI}" -f [OR]
RewriteCond "/home/matt/alt_htdocs%{REQUEST_URI}" -d
RewriteRule ^/?(.*)$ /home/matt/alt_htdocs/$1 [L]

<Directory /home/matt/alt_htdocs>
  Order allow,deny
  Allow from all
</Directory>

If you don't want to serve directories from the alternate docroot (Options +Indexes) then you don't need the RewriteCond with the -d. Remove it and the [OR] from the previous line.

If you want to add a restriction to the alternate docroot, for example only allow the images directory, then put that in your regex:

RewriteCond "/home/matt/alt_htdocs%{REQUEST_URI}" -f [OR]
RewriteCond "/home/matt/alt_htdocs%{REQUEST_URI}" -d
RewriteRule ^/?(images/.+)$ /home/matt/alt_htdocs/$1 [L]

If you want three or four or five alternate docroots, just copy-and-paste the three rewrite lines as necessary. And remember you may need a <Directory> section for each location.

How it works

If you see a file or directory in the alternate docroot, then serve it. Otherwise: DocumentRoot.

The RewriteConds look into the alternate docroot, and checks if a file (-f) or directory (-d) exists under there that matches the REQUEST_URI, which is something like /index.php or /images/b.png.

If either/both of the RewriteConds match, then the RewriteRule runs. If its regex matches, then we give Apache the explicit filesystem path it should serve. If no such rules match, then the DocumentRoot comes into play.

In some situations (if you are doing further rewrites) then you may not want the [L], which instructs mod_rewrite to run no further rules for this request.

Untested

Today is the first day I've used this hack, so YMMV. But it seems to work very well for static files. Preliminary testing also indicates it works for .php files (in both the normal and alternate docroot). I doubt it would work if trying to include one .php into another across docroots.

The Use Case

Why would anyone, or even myself, find this useful?

Websites tend to end up like my son's food at the end of dinner: all mushed up into a claggy mess. You may be running a couple of PHP apps, a couple of your own apps, in addition to a bunch of static files. And you may care about permalinks. And sometimes all these things overlap in the filesystem.

One solution is to copy everything into the one docroot. When it comes time to upgrade a component, you have to selectively pull out the old bits and dump in the new bits, without overwriting everything else. That sucks, and is why I always hate upgrading my WordPress instance.

The other solution is to put each component in its own directory. Upgrading a component is then the graceful process of deleting a directory and replacing it with new content. Separate directories can also have differing permissions, so that they can be edited by different people or programs.

If your components don't overlap, then vanilla rewrites or Aliases work very well. Otherwise this hack may come in handy.

Further work

I'm sure this could be done much more easily with a custom module, in the vein of VirtualDocumentRoot. Ideally it would be nice to say (with options for specifying priority):

DocumentRoot /usr/local/apache/htdocs
AltDocumentRoot /home/matt/alt_htdocs

It would be also nice to be able to support parallel-overlapping views in the filesystem proper. A kind of "mount" that presented a view of several parallel directories.

  • Home
  • Blog