hatestheinternet
post image is floppy disks for breakfast by Blude

Serving Content

This part I broke out because apparently I'm kinda long-winded and my last post was hella long, partly because it's pretty much Drupal-specific. Since the idea is just to drop sites in a folder and have them Magically Work(tm), our content virtual host definition also needs zero knowledge of the domains it's hosting.As you might expect, it kind of looks like our static definition:

<VirtualHost *:80>
        ServerName content.domain.com
        ServerAlias content.*
        DocumentRoot /web/drupal/docs
        AccessFileName .htaccess.content
</VirtualHost>

As you can see, it's pretty much exactly the same as our static host, except it uses a differently named htaccess, which starts off pretty much the same:

Options -MultiViews
ErrorDocument 404 default

RemoveHandler .php .phtml .php3
RemoveType .php .phtml .php3
php_flag engine off

RewriteEngine On

SetEnvIf Host ^content\.(.+)$ APEX_HOST=$1
SetEnvIf Host \.([^\.]+\..{2,6})\/?$ MAIN_DOMAIN=$1

RewriteRule "(^|/)\." - [R=404,L]

RewriteCond %{HTTP_HOST} ^content\.(.+)$
RewriteRule . - [S=1,E=APEX_HOST:%1]

RewriteRule . - [R=403,E=NOEXPIRE:1,L]

RewriteCond %{HTTP_HOST} \.([^\.]+\..{2,6})\/?$
RewriteRule . - [E=MAIN_DOMAIN:%1]

RewriteCond %{REQUEST_FILENAME} ^\.
RewriteRule . - [R=404,L]

Like static, we set some options, remove any hint of PHP files being executable, and extract our APEX_HOST (ie. content.dev.domain.com would contain dev.domain.com) and our MAIN_DOMAIN (ie domain.com). Finally, make sure the host being requested is content.(something), 403 if it's not.

You'll also probably notice that I set the APEX_HOST and MAIN_DOMAIN environment variables twice. I kept running in to cases where one wouldn't set it for the other and this seems to work.

Finally, to keep prying eyes at bay, we 404 any file that starts with a dot.

RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_URI} !^sites/([^\.]+\..{2,6})/
RewriteRule . sites/%{ENV:MAIN_DOMAIN}/files%{REQUEST_URI} [QSA,L]

This snippet is why we extracted the main domain from our apex host. If the file doesn't exist and the URI doesn't start with sites/[MAIN_DOMAIN], we rewrite the request to sites/[MAIN_DOMAIN]/files/[REQUEST_URI]. We also use the QSA option because of the next little bit:

RewriteCond %{REQUEST_URI} ^/(.*)/styles/(.*)
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule . http://%{ENV:APEX_HOST}/sites/%{ENV:MAIN_DOMAIN}/files/styles/%2 [E=NOEXPIRE:1,R=307,QSA,L]

Since Drupal generates image derivatives on demand, we need to redirect requests for missing image renditions (which live in sites/whatever/files/styles) back to our apex host to be generated. We explicitly set NOEXPIRE even though we don't really have to because most CDNs and browsers shouldn't cache 307/Temporary Redirects. We also make sure to append the query string since the token needs to be preserved.

Header set Cache-Control "max-age=31536000" env=!NOEXPIRE
Header always set Cache-Control "max-age=0" env=NOEXPIRE

Finally, unless we're explicitly told not to cache something, we spit out a 31,536,000 second (1 yearish) time to live. The only thing left, just because we never know what'll happen in the future, we set up CORS:

Header set Access-Control-Allow-Origin http://%{APEX_HOST}e/ env=APEX_HOST

So, there we have it: A virtual host serving out of a Drupal multi-site installation with absolutely no knowledge of anything. You might be wondering why I went through all the pain in the ass of setting this up, but we'll get to that later.