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.