We like to use our own site to experiment with different technologies. CDN's are nothing new, and Metal Toad has projects running on competing systems including Akamai and Level 3. Still, I think Amazon Cloudfront is an interesting offering and I wanted to give it a spin. Here's my review of the service after setting it up with Drupal:
Pros:
- Easy setup, low-cost, only pay for what you use
- Support for CNAMES and domain sharding (e.g. static[1234].metaltoad.com)
- Supports
Accept:Byte-Range
headers (important for media files)
- Supports gzip compression (assuming your origin server does the compression)
- Supports HTTPS
- Honors cache-control headers
- Custom HTTP headers are passed through to edge requests (including CORS)
Cons:
- No global or directory "purge" command - each file must be invalidated individually
- No custom SSL Certificates, so you can't use CNAMES with HTTPS
- Fussy about SSL ciphers on the origin server (Cloudfront wasn't compatible with the ciphers configured on our load balancer, so I had to use "http-only" instead of "match-viewer")
- Only minimal logging, no reports or graphs
Domain sharding
Multiple CNAMES add a little overhead with extra DNS lookups, but increase the number of parallel downloads (browsers impose a per-hostname limit). To minimize upstream bandwidth needed, these domains should be cookie-free. Since I chose subdomains of the site itself, I needed to adjust some cookie settings in Drupal:
- In settings.php, set
$cookie_domain = 'www.metaltoad.com';
- In the Google Analytics module, set the tracking to "One domain with multiple subdomains"
Thanks to some new alter hooks in Drupal 7, all you need to implement a static file CDN is hook_file_url_alter()
. There's an actively maintained CDN module, but in the spirit of inquiry I decided to implement the hook directly.
Module file
/**
* Implements hook_file_url_alter().
*/
function mymodule_file_url_alter(&$uri) {
// Route static files to Amazon CloudFront.
if ($_SERVER['HTTP_HOST'] == 'www.metaltoad.com') {
if ($GLOBALS['is_https']) {
// Cloudfront doesn't support custom SSL certs, so we need to use Amazon's.
$cdn = 'https://abcdef12345.cloudfront.net';
}
else {
// Multiple hostnames to parallelize downloads.
$shard = crc32($uri) % 4 + 1;
$cdn = "http://static$shard.metaltoad.com";
}
$scheme = file_uri_scheme($uri);
if ($scheme == 'public') {
$wrapper = file_stream_wrapper_get_instance_by_scheme('public');
$path = $wrapper->getDirectoryPath() . '/' . file_uri_target($uri);
$uri = "$cdn/$path";
}
else if (!$scheme && strpos($uri, '//') !== 0) {
$uri = "$cdn/$uri";
}
}
}
/**
* Implements hook_boot().
*/
function mymodule_boot() {
// Make sure Amazon CloudFront doesn't serve dynamic content.
if (!empty($_SERVER['HTTP_X_AMZ_CF_ID']) && !strstr($_GET['q'], 'files/styles')) {
header("HTTP/1.0 404 Not Found");
print '404 Not Found';
exit();
}
}
/**
* Implements hook_css_alter().
*/
function mymodule_css_alter(&$css) {
// Mangle the paths slightly so that drupal_build_css_cache() will generate
// different keys on HTTPS. Necessary because CDN URL varies by protocol.
if ($GLOBALS['is_https']) {
foreach ($css as $key => $style) {
if ($style['preprocess'] && $style['type'] == 'file') {
$css[$key]['data'] = './' . $style['data'];
}
}
}
}
.htaccess
# Set CORS header on static assets for CDN.
<FilesMatch "\.(ttf|otf|eot|woff|css|css\.gz|js|js\.gz)$">
<IfModule mod_headers.c>
Header set Access-Control-Allow-Origin "*"
</IfModule>
</FilesMatch>
The HTTPS and sharding support adds a little complexity, but overall the integration is straightforward. I'd recommend Cloudfront to anyone who wants an easy and cost-effective scalability win.