Serving your Sitecore Media items, on public sites, through a Content Delivery Network (CDN) is always good. I like using Amazon CloudFront since it’s really cheap and easy to set up. For large, high volume sites, I’d look at alternatives as well. But since there is no startup cost or fixed monthly fees etc using CloudFront, I think all small and mid size Sitecore setups should leverage from it.
There are quite many tutorials out there on how to configure Sitecore for CloudFront, so I won’t get into that here. Maybe I’ll post something later..
There are a few minor problems with CloudFront though. One is a nasty bug that spoils proper caching.
The problem, in brief, is that when the web server sets both the Cache-Control max-age header and the Expires header, CloudFront complies to the Cache-Control header, but it also stores the Expires header. When the max-age limit is passed, CloudFront refetches the item from the origin and Sitecore responds with a 304 not modified. Since it’s not modified, CloudFront doesn’t update its cached headers either, so now the Expires header is in the past.
The way to solve this is to modify the MediaRequestHandler in Sitecore, so that it doesn’t set the Expires header, like this:
public class CloudFrontMediaRequestHandler : MediaRequestHandler { /// /// This code is a complete copy of the original code, but SetExpires is removed. /// protected override void SendMediaHeaders(Media media, System.Web.HttpContext context) { DateTime date = media.MediaData.Updated; if (date > DateTime.Now) date = DateTime.Now; HttpCachePolicy cache = context.Response.Cache; cache.SetLastModified(date); cache.SetETag(media.MediaData.MediaId); cache.SetCacheability(Settings.MediaResponse.Cacheability); TimeSpan delta = Settings.MediaResponse.MaxAge; if (delta > TimeSpan.Zero) { if (delta > TimeSpan.FromDays(365.0)) delta = TimeSpan.FromDays(365.0); cache.SetMaxAge(delta); //cache.SetExpires(DateTime.Now + delta); // This shouldn't be here. } Tristate slidingExpiration = Settings.MediaResponse.SlidingExpiration; if (slidingExpiration != Tristate.Undefined) cache.SetSlidingExpiration(slidingExpiration == Tristate.True); string cacheExtensions = Settings.MediaResponse.CacheExtensions; if (cacheExtensions.Length <= 0) return; cache.AppendCacheExtension(cacheExtensions); } } [/sourcecode] Then change your web.config to use this new class: [sourcecode language="xml"] <add verb="*" path="sitecore_media.ashx" type="YourNamespace.CloudFrontMediaRequestHandler, YourAssembly" name="Sitecore.MediaRequestHandler" />
Please not that I haven’t tested this code extensively yet – I need some items to expire first 😉 The Date and ETag headers may need some changes as well in order to work properly.
Another minor problem with CloudFront is that the AWS Management Console doesn’t allow you to create invalidation requests on URL’s starting with tilde (~), claiming it’s an invalid URL. So now you have yet another reason to follow Alex Shybas recommendations on chaining the MediaLinkPrefix.
Basically, any element that matches (css/img/js/swf etc) will have an expires header of 600 seconds added to it before it is sent to the browser. Note, this snippet may not do what it is supposed to. Many shared hosts remove the ability to turn this on using htaccess. So, check with your host if it doesn’t seem to do the trick. How do you know if it’s working? Run YSlow Performance again and click on the Components tab. It will list every element and whether there is an expire time associated with it.