<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>2tap.com &#187; Subversion</title>
	<atom:link href="http://2tap.com/category/subversion/feed/" rel="self" type="application/rss+xml" />
	<link>http://2tap.com</link>
	<description>Random projects and stuff by Russ Hall in London</description>
	<lastBuildDate>Sun, 28 Feb 2010 18:08:17 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.1</generator>
		<item>
		<title>Efficient caching of versioned JavaScript, CSS and image assets for fun and profit</title>
		<link>http://2tap.com/2009/05/18/efficient-caching-of-versioned-javascript-css-and-image-assets-for-fun-and-profit/</link>
		<comments>http://2tap.com/2009/05/18/efficient-caching-of-versioned-javascript-css-and-image-assets-for-fun-and-profit/#comments</comments>
		<pubDate>Mon, 18 May 2009 18:23:33 +0000</pubDate>
		<dc:creator>Russ</dc:creator>
				<category><![CDATA[JavaScript]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[Subversion]]></category>

		<guid isPermaLink="false">http://2tap.com/?p=89</guid>
		<description><![CDATA[&#8220;The new image is showing but I think it&#8217;s using the old stylesheet!&#8221; Sound familiar? Caching? Caching of a web page&#8217;s assets such as CSS and image files can be a double-edged sword. On the one hand, if done right, it can lead to much faster load times with less strain on the server. If [...]]]></description>
			<content:encoded><![CDATA[<p><strong><em>&#8220;The new image is showing but I think it&#8217;s using the old stylesheet!&#8221;</em></strong></p>
<p>Sound familiar?</p>
<h3>Caching?</h3>
<p>Caching of a web page&#8217;s assets such as CSS and image files can be a double-edged sword. On the one hand, if done right, it can lead to much faster load times with less strain on the server. If done incorrectly, or worse not even considered, developers are opening themselves up to all kinds of synchronisation issues whenever files are modified.</p>
<p>In a typical web application, certain assets rarely change. Common theme images and JavaScript libraries are a good example of this. On the other hand, CSS files and the site&#8217;s core JavaScript functionality are prime candidates for frequent change but it is not an exact science and generally impossible to predict.</p>
<p>Caching of assets is the browser&#8217;s default behaviour. If an expiry time is not specifically set, it is up to the browser to decide how long to wait before checking the server for a new version. Once a file is in a browsers cache you&#8217;re at the mercy of the browser as to when the user will see the new version. Minutes? Hours? Days? Who knows. Your only option is to rename the asset in order to force the new version to be fetched.</p>
<p>So caching is evil, right? Well, no. With a little forethought, caching is your friend. And the user&#8217;s friend. And the web server&#8217;s friend. Treated right, it&#8217;s the life of the party.</p>
<p>Imagine your site is deployed once and nothing changes for eternity. The optimal caching strategy here is to instruct the browser to cache everything indefinitely. This means that, after the first visit, a user may never have to contact the server again. Load times are speedy. Your server&#8217;s relaxed. All is well. The problem, of course, is that any changes you <strong>do</strong> inevitably make will never be shown to users who have the site in their cache. At least, not without renaming the changed asset so the browser considers it a new file.</p>
<p>So the problem is that we want the browser to cache everything forever. Unless we change something. And we want the browser to know when we do this. Without asking us. And it&#8217;d be nice if this was automated. Ideas?</p>
<h3>Option One &#8211; Set an expiry date in the past for all assets</h3>
<p>Never cache anything!</p>
<p>Not really an option, but it <strong>does</strong> solve half of the problem. The browser will never cache anything and so the user will always see the latest version of all site assets. It works, but we&#8217;re completely missing out on one of the main benefits of caching &#8211; faster loading for the user and less stress on the server. Next.</p>
<h3>Option Two &#8211; Include a site version string in every URL</h3>
<p>One commonly used strategy is to include a unique identifier in every URL which is changed whenever the site is deployed. For example, an image at the following URL:</p>

<div class="wp_syntax"><div class="code"><pre class="bash" style="font-family:monospace;"><span style="color: #000000; font-weight: bold;">/</span>images<span style="color: #000000; font-weight: bold;">/</span>logo.png</pre></div></div>

<p>Would become:</p>

<div class="wp_syntax"><div class="code"><pre class="bash" style="font-family:monospace;"><span style="color: #000000; font-weight: bold;">/</span>images<span style="color: #000000; font-weight: bold;">/</span>logo.82.png</pre></div></div>

<p>Here, 82 is a unique identifier. With some Apache mod_rewrite trickery, we can transparently map this to the original URL. As far as the browser is concerned, this is a different file to the previous <em>logo.81.png</em> image and so any existing cache of this file is ignored.</p>
<p>Generally, this technique is employed in a semi-automated way. The version number can either be set manually in a configuration file (for example) or pulled from the repository version number. With this technique, all assets can be set to cache indefinitely.</p>
<p>The above is a pretty good solution. I&#8217;ve used it myself. But it&#8217;s not the most optimal. Every time a new version of the site is deployed, any assets in the users cache are invalidated. The whole site needs to be downloaded again. If site updates are infrequent, this isn&#8217;t <strong>too</strong> much of a problem. It sure as hell beats never caching anything or, worse, leaving the browser to decide how long to cache each item.</p>
<h3>Option Three &#8211; Fine grained caching + Automated!</h3>
<p>Clearly, the solution is to include a unique version string <strong>per file</strong>. This means that every file is considered independently and will only be re-downloaded if it has actually changed. One technique for doing this is to use the files last-modified timestamp. This gives a unique ID for the file which will change every time the file contents change. If the file is under version control (your projects <strong>are</strong> versioned, right?) we can&#8217;t use the modified timestamp as-is since it will change whenever the file is checked out. But we can find out what revision the file was changed in (under SVN at least) so we&#8217;re still good to go.</p>
<p>The goal is as follows: To instruct the browser to cache all assets (in this case, JavaScript, CSS and all image files) indefinitely. Whenever an asset changes, we want the URL to also change. The result of this is that whenever we deploy a new version of the site, only assets that have actually changed will be given a new URL. So if you&#8217;ve only changed one CSS file and a couple of images, repeat visits to the site will only need to re-download these files. We&#8217;d also like it to be automated. Only a masochist would attempt to manually change URLs whenever something changes on any sufficiently complex site.</p>
<p>Presented here is an automated solution for efficient caching using a bit of PHP and based on a site in an SVN repository. It&#8217;s also based around Linux. It could easily be adapted to other scripting languages, operating systems and/or version control systems &#8211; these technologies are merely presented here as an example.</p>
<p>To achieve the automated part, we need to run a script on the checked out version of the site prior to its deployment. The script will search the project for URLs (for a specific set of assets) and will rewrite the URL for any that it finds including a unique identifier. In our case, we&#8217;ll use the <strong>svn info</strong> command to find out the last revision the file actually changed in. Another approach would be to simply take a hash of the file contents (<a href="http://en.wikipedia.org/wiki/MD5">md5</a> would be a good candidate) and use this as its last-changed-identifier.</p>
<p>Rather than renaming each file to match the included identifier we set in the URL, we&#8217;ll use mod_rewrite within Apache to match a given format of URL back to its original. So <strong>myasset.123.png</strong> will be transparently mapped back to its original <strong>myasset.png</strong> filename.</p>
<p>Here&#8217;s a quick script I knocked up in PHP to facilitate this process. It should be run on a checked out working copy. It scans a given directory for files of a given type (in my base, &#8220;<strong>.tpl</strong>&#8221; (HTML templates) and <strong>.css</strong> files). Within each file it finds, it looks for any assets of a given type referenced in applicable areas (href and src attributes in HTML, url() in CSS). It then converts each URL to a filesystem path and checks the working copy for its existence. If it finds it, the URL is rewritten to include the last modified version number (pulled from <strong>svn info</strong>). Once this is done we just need to include an Apache mod_rewrite rule as discussed above.</p>
<h3>The PHP</h3>

<div class="wp_syntax"><div class="code"><pre class="php" style="font-family:monospace;"><span style="color: #339933;">&lt;</span> ?php
&nbsp;
<span style="color: #666666; font-style: italic;">//</span>
<span style="color: #666666; font-style: italic;">// config</span>
<span style="color: #666666; font-style: italic;">//</span>
<span style="color: #000088;">$arr_config</span> <span style="color: #339933;">=</span> <span style="color: #990000;">array</span><span style="color: #009900;">&#40;</span>
&nbsp;
    <span style="color: #666666; font-style: italic;">// file types to check within for assets to version</span>
    <span style="color: #0000ff;">'file_extensions'</span> <span style="color: #339933;">=&gt;</span> <span style="color: #990000;">array</span><span style="color: #009900;">&#40;</span><span style="color: #0000ff;">'tpl'</span><span style="color: #339933;">,</span> <span style="color: #0000ff;">'css'</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">,</span>
&nbsp;
    <span style="color: #666666; font-style: italic;">// asset extensions to version</span>
    <span style="color: #0000ff;">'asset_extensions'</span> <span style="color: #339933;">=&gt;</span> <span style="color: #990000;">array</span><span style="color: #009900;">&#40;</span><span style="color: #0000ff;">'jpg'</span><span style="color: #339933;">,</span> <span style="color: #0000ff;">'jpeg'</span><span style="color: #339933;">,</span> <span style="color: #0000ff;">'png'</span><span style="color: #339933;">,</span> <span style="color: #0000ff;">'gif'</span><span style="color: #339933;">,</span> <span style="color: #0000ff;">'css'</span><span style="color: #339933;">,</span> <span style="color: #0000ff;">'ico'</span><span style="color: #339933;">,</span> <span style="color: #0000ff;">'js'</span><span style="color: #339933;">,</span> <span style="color: #0000ff;">'htc'</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">,</span>
&nbsp;
    <span style="color: #666666; font-style: italic;">// filesystem path to the webroot of the application (so we can translate</span>
    <span style="color: #666666; font-style: italic;">// relative urls to the actual path on the filesystem)</span>
    <span style="color: #0000ff;">'webroot'</span> <span style="color: #339933;">=&gt;</span> <span style="color: #990000;">dirname</span><span style="color: #009900;">&#40;</span><span style="color: #009900; font-weight: bold;">__FILE__</span><span style="color: #009900;">&#41;</span> <span style="color: #339933;">.</span> <span style="color: #0000ff;">'/../www'</span><span style="color: #339933;">,</span>
&nbsp;
    <span style="color: #666666; font-style: italic;">// regular expressions to match assets</span>
    <span style="color: #0000ff;">'regex'</span> <span style="color: #339933;">=&gt;</span> <span style="color: #990000;">array</span><span style="color: #009900;">&#40;</span>
        <span style="color: #0000ff;">'/(?:src|href)=&quot;(.*)&quot;/iU'</span><span style="color: #339933;">,</span> <span style="color: #666666; font-style: italic;">// match assets in src and href attributes</span>
        <span style="color: #0000ff;">'/url\((.*)\)/iU'</span>          <span style="color: #666666; font-style: italic;">// match assets in CSS url() properties</span>
    <span style="color: #009900;">&#41;</span>
<span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
&nbsp;
<span style="color: #666666; font-style: italic;">//</span>
<span style="color: #666666; font-style: italic;">// arguments</span>
<span style="color: #666666; font-style: italic;">//</span>
&nbsp;
<span style="color: #666666; font-style: italic;">// we require just one argument, the root path to search for files</span>
<span style="color: #b1b100;">if</span><span style="color: #009900;">&#40;</span><span style="color: #339933;">!</span><span style="color: #990000;">isset</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$_SERVER</span><span style="color: #009900;">&#91;</span><span style="color: #0000ff;">'argv'</span><span style="color: #009900;">&#93;</span><span style="color: #009900;">&#91;</span><span style="color: #cc66cc;">1</span><span style="color: #009900;">&#93;</span><span style="color: #009900;">&#41;</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
    <span style="color: #990000;">die</span><span style="color: #009900;">&#40;</span><span style="color: #0000ff;">&quot;Error: first argument must be the path to your working copy<span style="color: #000099; font-weight: bold;">\n</span>&quot;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
<span style="color: #009900;">&#125;</span>
&nbsp;
<span style="color: #666666; font-style: italic;">//</span>
<span style="color: #666666; font-style: italic;">// execute</span>
<span style="color: #666666; font-style: italic;">//</span>
version_assets<span style="color: #009900;">&#40;</span><span style="color: #000088;">$_SERVER</span><span style="color: #009900;">&#91;</span><span style="color: #0000ff;">'argv'</span><span style="color: #009900;">&#93;</span><span style="color: #009900;">&#91;</span><span style="color: #cc66cc;">1</span><span style="color: #009900;">&#93;</span><span style="color: #339933;">,</span> <span style="color: #000088;">$arr_config</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
&nbsp;
&nbsp;
&nbsp;
&nbsp;
<span style="color: #009933; font-style: italic;">/**
 * Checks each file in the passed path recursively to see if there are any assets
 * to version.
 *
 * Only file extensions defined in the config are checked and then only assets matching
 * a particular filetype are versioned.
 *
 * If an asset referenced is not found on the filesystem or is not under version control
 * within the working copy, the asset is ignored and nothing is changed.
 *
 * @param str $str_search_path    Path to begin scanning of files
 * @param arr $arr_config         Configuration params determining which files to check, which
 *                                asset extensions to check etc.
 * @return void
 */</span>
<span style="color: #000000; font-weight: bold;">function</span> version_assets<span style="color: #009900;">&#40;</span><span style="color: #000088;">$str_search_path</span><span style="color: #339933;">,</span> <span style="color: #000088;">$arr_config</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
&nbsp;
    <span style="color: #666666; font-style: italic;">// pull in filenames to check</span>
    <span style="color: #000088;">$arr_files</span> <span style="color: #339933;">=</span> get_files_recursive<span style="color: #009900;">&#40;</span><span style="color: #000088;">$str_search_path</span><span style="color: #339933;">,</span> <span style="color: #000088;">$arr_config</span><span style="color: #009900;">&#91;</span><span style="color: #0000ff;">'file_extensions'</span><span style="color: #009900;">&#93;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
&nbsp;
    <span style="color: #b1b100;">foreach</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$arr_files</span> <span style="color: #b1b100;">as</span> <span style="color: #000088;">$str_file</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
&nbsp;
        <span style="color: #666666; font-style: italic;">// load the file into memory</span>
        <span style="color: #000088;">$str_file_content</span> <span style="color: #339933;">=</span> <span style="color: #990000;">file_get_contents</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$str_file</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
&nbsp;
        <span style="color: #666666; font-style: italic;">// look for any matching assets in the regex list defined in the config</span>
        <span style="color: #000088;">$arr_matches</span> <span style="color: #339933;">=</span> <span style="color: #990000;">array</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
&nbsp;
        <span style="color: #b1b100;">foreach</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$arr_config</span><span style="color: #009900;">&#91;</span><span style="color: #0000ff;">'regex'</span><span style="color: #009900;">&#93;</span> <span style="color: #b1b100;">as</span> <span style="color: #000088;">$str_regex</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
&nbsp;
            <span style="color: #b1b100;">if</span><span style="color: #009900;">&#40;</span><span style="color: #990000;">preg_match_all</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$str_regex</span><span style="color: #339933;">,</span> <span style="color: #000088;">$str_file_content</span><span style="color: #339933;">,</span> <span style="color: #000088;">$arr_m</span><span style="color: #009900;">&#41;</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
                <span style="color: #000088;">$arr_matches</span> <span style="color: #339933;">=</span> <span style="color: #990000;">array_merge</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$arr_matches</span><span style="color: #339933;">,</span> <span style="color: #000088;">$arr_m</span><span style="color: #009900;">&#91;</span><span style="color: #cc66cc;">1</span><span style="color: #009900;">&#93;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
            <span style="color: #009900;">&#125;</span>
        <span style="color: #009900;">&#125;</span>
&nbsp;
        <span style="color: #666666; font-style: italic;">// filter out any matches that do not have an extension defined in the asset list</span>
        <span style="color: #000088;">$arr_matches_filtered</span> <span style="color: #339933;">=</span> <span style="color: #990000;">array</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
&nbsp;
        <span style="color: #b1b100;">foreach</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$arr_matches</span> <span style="color: #b1b100;">as</span> <span style="color: #000088;">$str_match</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
&nbsp;
            <span style="color: #000088;">$arr_url</span> <span style="color: #339933;">=</span> <span style="color: #990000;">parse_url</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$str_match</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
            <span style="color: #000088;">$str_asset</span> <span style="color: #339933;">=</span> <span style="color: #000088;">$arr_url</span><span style="color: #009900;">&#91;</span><span style="color: #0000ff;">'path'</span><span style="color: #009900;">&#93;</span><span style="color: #339933;">;</span>
&nbsp;
            <span style="color: #b1b100;">if</span><span style="color: #009900;">&#40;</span><span style="color: #990000;">preg_match</span><span style="color: #009900;">&#40;</span><span style="color: #0000ff;">'/\.('</span> <span style="color: #339933;">.</span> <span style="color: #990000;">implode</span><span style="color: #009900;">&#40;</span><span style="color: #0000ff;">'|'</span><span style="color: #339933;">,</span> <span style="color: #000088;">$arr_config</span><span style="color: #009900;">&#91;</span><span style="color: #0000ff;">'asset_extensions'</span><span style="color: #009900;">&#93;</span><span style="color: #009900;">&#41;</span> <span style="color: #339933;">.</span> <span style="color: #0000ff;">'$)/iU'</span><span style="color: #339933;">,</span> <span style="color: #000088;">$str_asset</span><span style="color: #009900;">&#41;</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
                <span style="color: #000088;">$arr_matches_filtered</span><span style="color: #009900;">&#91;</span><span style="color: #009900;">&#93;</span> <span style="color: #339933;">=</span> <span style="color: #000088;">$str_asset</span><span style="color: #339933;">;</span>
            <span style="color: #009900;">&#125;</span>
        <span style="color: #009900;">&#125;</span>
&nbsp;
        <span style="color: #666666; font-style: italic;">// if we've found any matches, process them</span>
        <span style="color: #b1b100;">if</span><span style="color: #009900;">&#40;</span><span style="color: #990000;">count</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$arr_matches_filtered</span><span style="color: #009900;">&#41;</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
&nbsp;
            <span style="color: #666666; font-style: italic;">// flag to determine if we need to write any changes back once we've processed</span>
            <span style="color: #666666; font-style: italic;">// each match</span>
            <span style="color: #000088;">$boo_modified_file</span> <span style="color: #339933;">=</span> <span style="color: #009900; font-weight: bold;">false</span><span style="color: #339933;">;</span>
&nbsp;
            <span style="color: #b1b100;">foreach</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$arr_matches_filtered</span> <span style="color: #b1b100;">as</span> <span style="color: #000088;">$str_url_asset</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
&nbsp;
                <span style="color: #666666; font-style: italic;">// use parse_url to extract just the path</span>
                <span style="color: #000088;">$arr_parsed</span> <span style="color: #339933;">=</span> <span style="color: #990000;">parse_url</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$str_url_asset</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
                <span style="color: #000088;">$str_url_path</span> <span style="color: #339933;">=</span> <span style="color: #000088;">$arr_parsed</span><span style="color: #009900;">&#91;</span><span style="color: #0000ff;">'path'</span><span style="color: #009900;">&#93;</span> <span style="color: #339933;">.</span> <span style="color: #339933;">@</span><span style="color: #000088;">$arr_parsed</span><span style="color: #009900;">&#91;</span><span style="color: #0000ff;">'query'</span><span style="color: #009900;">&#93;</span> <span style="color: #339933;">.</span> <span style="color: #339933;">@</span><span style="color: #000088;">$arr_parsed</span><span style="color: #009900;">&#91;</span><span style="color: #0000ff;">'fragment'</span><span style="color: #009900;">&#93;</span><span style="color: #339933;">;</span>
&nbsp;
                <span style="color: #666666; font-style: italic;">// if this is a relative url (e.g. begininng ../) then work out the filesystem path</span>
                <span style="color: #666666; font-style: italic;">// based on the location of the file containing the asset</span>
                <span style="color: #b1b100;">if</span><span style="color: #009900;">&#40;</span><span style="color: #990000;">strpos</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$str_url_path</span><span style="color: #339933;">,</span> <span style="color: #0000ff;">'../'</span><span style="color: #009900;">&#41;</span> <span style="color: #339933;">===</span> <span style="color: #cc66cc;">0</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
                    <span style="color: #000088;">$str_fs_path</span> <span style="color: #339933;">=</span> <span style="color: #000088;">$arr_config</span><span style="color: #009900;">&#91;</span><span style="color: #0000ff;">'webroot'</span><span style="color: #009900;">&#93;</span> <span style="color: #339933;">.</span> <span style="color: #0000ff;">'/'</span> <span style="color: #339933;">.</span> <span style="color: #990000;">dirname</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$str_file</span><span style="color: #009900;">&#41;</span> <span style="color: #339933;">.</span> <span style="color: #0000ff;">'/'</span> <span style="color: #339933;">.</span> <span style="color: #000088;">$str_url_path</span><span style="color: #339933;">;</span>
                <span style="color: #009900;">&#125;</span>
                <span style="color: #b1b100;">else</span> <span style="color: #009900;">&#123;</span>
                    <span style="color: #000088;">$str_fs_path</span> <span style="color: #339933;">=</span> <span style="color: #000088;">$arr_config</span><span style="color: #009900;">&#91;</span><span style="color: #0000ff;">'webroot'</span><span style="color: #009900;">&#93;</span> <span style="color: #339933;">.</span> <span style="color: #0000ff;">'/'</span> <span style="color: #339933;">.</span> <span style="color: #000088;">$str_url_path</span><span style="color: #339933;">;</span>
                <span style="color: #009900;">&#125;</span>
&nbsp;
                <span style="color: #666666; font-style: italic;">// normalise path with realpath</span>
                <span style="color: #000088;">$str_fs_path</span> <span style="color: #339933;">=</span> <span style="color: #990000;">realpath</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$str_fs_path</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
&nbsp;
                <span style="color: #666666; font-style: italic;">// only proceed if the file exists</span>
                <span style="color: #b1b100;">if</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$str_fs_path</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
&nbsp;
                    <span style="color: #666666; font-style: italic;">// execute the svn info command to retrieve the change information</span>
                    <span style="color: #000088;">$str_svn_result</span> <span style="color: #339933;">=</span> <span style="color: #339933;">@</span><span style="color: #990000;">shell_exec</span><span style="color: #009900;">&#40;</span><span style="color: #0000ff;">'svn info '</span> <span style="color: #339933;">.</span> <span style="color: #000088;">$str_fs_path</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
                    <span style="color: #000088;">$arr_svn_matches</span> <span style="color: #339933;">=</span> <span style="color: #990000;">array</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
&nbsp;
                    <span style="color: #666666; font-style: italic;">// extract the last changed revision to use as the version</span>
                    <span style="color: #990000;">preg_match</span><span style="color: #009900;">&#40;</span><span style="color: #0000ff;">'/Last Changed Rev: ([0-9]+)/i'</span><span style="color: #339933;">,</span> <span style="color: #000088;">$str_svn_result</span><span style="color: #339933;">,</span> <span style="color: #000088;">$arr_svn_matches</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
&nbsp;
                    <span style="color: #666666; font-style: italic;">// only proceed if this file is in version control (e.g. we retrieved a valid match</span>
                    <span style="color: #666666; font-style: italic;">// from the regex above)</span>
                    <span style="color: #b1b100;">if</span><span style="color: #009900;">&#40;</span><span style="color: #990000;">count</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$arr_svn_matches</span><span style="color: #009900;">&#41;</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
&nbsp;
                        <span style="color: #000088;">$str_version</span> <span style="color: #339933;">=</span> <span style="color: #000088;">$arr_svn_matches</span><span style="color: #009900;">&#91;</span><span style="color: #cc66cc;">1</span><span style="color: #009900;">&#93;</span><span style="color: #339933;">;</span>
&nbsp;
                        <span style="color: #666666; font-style: italic;">// add version number into the file url (in the form asset.name.VERSION.ext)</span>
                        <span style="color: #000088;">$str_versioned_url</span> <span style="color: #339933;">=</span> <span style="color: #990000;">preg_replace</span><span style="color: #009900;">&#40;</span><span style="color: #0000ff;">'/(.*)(\.[a-zA-Z0-9]+)$/'</span><span style="color: #339933;">,</span> <span style="color: #0000ff;">'$1.'</span> <span style="color: #339933;">.</span> <span style="color: #000088;">$str_version</span> <span style="color: #339933;">.</span> <span style="color: #0000ff;">'$2'</span><span style="color: #339933;">,</span> <span style="color: #000088;">$str_url_asset</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
                        <span style="color: #000088;">$str_file_content</span> <span style="color: #339933;">=</span> <span style="color: #990000;">str_replace</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$str_url_asset</span><span style="color: #339933;">,</span> <span style="color: #000088;">$str_versioned_url</span><span style="color: #339933;">,</span> <span style="color: #000088;">$str_file_content</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
&nbsp;
                        <span style="color: #666666; font-style: italic;">// flag as</span>
                        <span style="color: #000088;">$boo_modified_file</span> <span style="color: #339933;">=</span> <span style="color: #009900; font-weight: bold;">true</span><span style="color: #339933;">;</span>
&nbsp;
                        <span style="color: #b1b100;">echo</span> <span style="color: #0000ff;">'Versioned: ['</span> <span style="color: #339933;">.</span> <span style="color: #000088;">$str_url_asset</span> <span style="color: #339933;">.</span> <span style="color: #0000ff;">'] referenced in file: ['</span> <span style="color: #339933;">.</span> <span style="color: #000088;">$str_file</span> <span style="color: #339933;">.</span> <span style="color: #0000ff;">']'</span> <span style="color: #339933;">.</span> <span style="color: #0000ff;">&quot;<span style="color: #000099; font-weight: bold;">\n</span>&quot;</span><span style="color: #339933;">;</span>
                    <span style="color: #009900;">&#125;</span>
                    <span style="color: #b1b100;">else</span> <span style="color: #009900;">&#123;</span>
                        <span style="color: #b1b100;">echo</span> <span style="color: #0000ff;">'Ignored: ['</span> <span style="color: #339933;">.</span> <span style="color: #000088;">$str_url_asset</span> <span style="color: #339933;">.</span> <span style="color: #0000ff;">'] referenced in file: ['</span> <span style="color: #339933;">.</span> <span style="color: #000088;">$str_file</span> <span style="color: #339933;">.</span> <span style="color: #0000ff;">'] (not versioned)'</span> <span style="color: #339933;">.</span> <span style="color: #0000ff;">&quot;<span style="color: #000099; font-weight: bold;">\n</span>&quot;</span><span style="color: #339933;">;</span>
                    <span style="color: #009900;">&#125;</span>
                <span style="color: #009900;">&#125;</span>
                <span style="color: #b1b100;">else</span> <span style="color: #009900;">&#123;</span>
                    <span style="color: #b1b100;">echo</span> <span style="color: #0000ff;">'Ignored: ['</span> <span style="color: #339933;">.</span> <span style="color: #000088;">$str_url_asset</span> <span style="color: #339933;">.</span> <span style="color: #0000ff;">'] referenced in file: ['</span> <span style="color: #339933;">.</span> <span style="color: #000088;">$str_file</span> <span style="color: #339933;">.</span> <span style="color: #0000ff;">'] (not on filesystem)'</span> <span style="color: #339933;">.</span> <span style="color: #0000ff;">&quot;<span style="color: #000099; font-weight: bold;">\n</span>&quot;</span><span style="color: #339933;">;</span>
                <span style="color: #009900;">&#125;</span>
            <span style="color: #009900;">&#125;</span>
&nbsp;
            <span style="color: #b1b100;">if</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$boo_modified_file</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
                <span style="color: #b1b100;">echo</span> <span style="color: #0000ff;">'-&gt; WRITING: '</span> <span style="color: #339933;">.</span> <span style="color: #000088;">$str_file</span> <span style="color: #339933;">.</span> <span style="color: #0000ff;">&quot;<span style="color: #000099; font-weight: bold;">\n</span>&quot;</span><span style="color: #339933;">;</span>
&nbsp;
                <span style="color: #666666; font-style: italic;">// write changes to this file back to the file system</span>
                <span style="color: #990000;">file_put_contents</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$str_file</span><span style="color: #339933;">,</span> <span style="color: #000088;">$str_file_content</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
            <span style="color: #009900;">&#125;</span>
        <span style="color: #009900;">&#125;</span>
    <span style="color: #009900;">&#125;</span>
<span style="color: #009900;">&#125;</span>
&nbsp;
<span style="color: #009933; font-style: italic;">/**
 * Utility method to recursively retrieve all files under a given directory. If
 * an optional array of extensions is passed, only these filetypes will be returned.
 *
 * Ignores any svn directories.
 *
 * @param str $str_path_start  Path to begin searching
 * @param mix $mix_extensions  Array of extensions to match or null to match any
 * @return array
 */</span>
<span style="color: #000000; font-weight: bold;">function</span> get_files_recursive<span style="color: #009900;">&#40;</span><span style="color: #000088;">$str_path_start</span><span style="color: #339933;">,</span> <span style="color: #000088;">$mix_extensions</span> <span style="color: #339933;">=</span> <span style="color: #009900; font-weight: bold;">null</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
&nbsp;
    <span style="color: #000088;">$arr_files</span> <span style="color: #339933;">=</span> <span style="color: #990000;">array</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
&nbsp;
    <span style="color: #b1b100;">if</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$obj_handle</span> <span style="color: #339933;">=</span> <span style="color: #990000;">opendir</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$str_path_start</span><span style="color: #009900;">&#41;</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
&nbsp;
        <span style="color: #b1b100;">while</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$str_file</span> <span style="color: #339933;">=</span> <span style="color: #990000;">readdir</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$obj_handle</span><span style="color: #009900;">&#41;</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
&nbsp;
            <span style="color: #666666; font-style: italic;">// ignore meta files and svn directories</span>
            <span style="color: #b1b100;">if</span><span style="color: #009900;">&#40;</span><span style="color: #339933;">!</span><span style="color: #990000;">in_array</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$str_file</span><span style="color: #339933;">,</span> <span style="color: #990000;">array</span><span style="color: #009900;">&#40;</span><span style="color: #0000ff;">'.'</span><span style="color: #339933;">,</span> <span style="color: #0000ff;">'..'</span><span style="color: #339933;">,</span> <span style="color: #0000ff;">'.svn'</span><span style="color: #009900;">&#41;</span><span style="color: #009900;">&#41;</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
&nbsp;
                <span style="color: #666666; font-style: italic;">// construct full path</span>
                <span style="color: #000088;">$str_path</span> <span style="color: #339933;">=</span> <span style="color: #000088;">$str_path_start</span> <span style="color: #339933;">.</span> <span style="color: #0000ff;">'/'</span> <span style="color: #339933;">.</span> <span style="color: #000088;">$str_file</span><span style="color: #339933;">;</span>
&nbsp;
                <span style="color: #666666; font-style: italic;">// if this is a directory, recursively retrieve its children</span>
                <span style="color: #b1b100;">if</span><span style="color: #009900;">&#40;</span><span style="color: #990000;">is_dir</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$str_path</span><span style="color: #009900;">&#41;</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
&nbsp;
                    <span style="color: #000088;">$arr_files</span> <span style="color: #339933;">=</span> <span style="color: #990000;">array_merge</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$arr_files</span><span style="color: #339933;">,</span> get_files_recursive<span style="color: #009900;">&#40;</span><span style="color: #000088;">$str_path</span><span style="color: #339933;">,</span> <span style="color: #000088;">$mix_extensions</span><span style="color: #009900;">&#41;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
                <span style="color: #009900;">&#125;</span>
&nbsp;
                <span style="color: #666666; font-style: italic;">// otherwise add to the list</span>
                <span style="color: #b1b100;">else</span> <span style="color: #009900;">&#123;</span>
&nbsp;
                    <span style="color: #666666; font-style: italic;">// only add if it's included in the extension list (if applicable)</span>
                    <span style="color: #b1b100;">if</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$mix_extensions</span> <span style="color: #339933;">==</span> <span style="color: #009900; font-weight: bold;">null</span> <span style="color: #339933;">||</span> <span style="color: #990000;">preg_match</span><span style="color: #009900;">&#40;</span><span style="color: #0000ff;">'/.*\.('</span> <span style="color: #339933;">.</span> <span style="color: #990000;">implode</span><span style="color: #009900;">&#40;</span><span style="color: #0000ff;">'|'</span><span style="color: #339933;">,</span> <span style="color: #000088;">$mix_extensions</span><span style="color: #009900;">&#41;</span> <span style="color: #339933;">.</span><span style="color: #0000ff;">')$/Ui'</span><span style="color: #339933;">,</span> <span style="color: #000088;">$str_file</span><span style="color: #009900;">&#41;</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span>
                        <span style="color: #000088;">$arr_files</span><span style="color: #009900;">&#91;</span><span style="color: #009900;">&#93;</span> <span style="color: #339933;">=</span> <span style="color: #990000;">str_replace</span><span style="color: #009900;">&#40;</span><span style="color: #0000ff;">'//'</span><span style="color: #339933;">,</span> <span style="color: #0000ff;">'/'</span><span style="color: #339933;">,</span> <span style="color: #000088;">$str_path</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
                    <span style="color: #009900;">&#125;</span>
                <span style="color: #009900;">&#125;</span>
            <span style="color: #009900;">&#125;</span>
        <span style="color: #009900;">&#125;</span>
&nbsp;
        <span style="color: #990000;">closedir</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$obj_handle</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
    <span style="color: #009900;">&#125;</span>
&nbsp;
    <span style="color: #b1b100;">return</span> <span style="color: #000088;">$arr_files</span><span style="color: #339933;">;</span>
<span style="color: #009900;">&#125;</span></pre></div></div>

<p>This is then executed like so:</p>

<div class="wp_syntax"><div class="code"><pre class="bash" style="font-family:monospace;">php version_assets.php <span style="color: #ff0000;">&quot;/path/to/project/checkout&quot;</span></pre></div></div>

<h3>The Apache config</h3>

<div class="wp_syntax"><div class="code"><pre class="apache" style="font-family:monospace;"><span style="color: #adadad; font-style: italic;">#</span>
<span style="color: #adadad; font-style: italic;"># Rewrite versioned asset urls</span>
<span style="color: #adadad; font-style: italic;">#</span>
<span style="color: #00007f;">RewriteEngine</span> <span style="color: #0000ff;">on</span>
<span style="color: #00007f;">RewriteRule</span> ^(.+)(\.[<span style="color: #ff0000;">0</span>-<span style="color: #ff0000;">9</span>]+)\.(js|css|jpg|jpeg|gif|png)$ $1.$3 [L]
&nbsp;
<span style="color: #adadad; font-style: italic;">#</span>
<span style="color: #adadad; font-style: italic;"># Set near indefinite expiry for certain assets</span>
<span style="color: #adadad; font-style: italic;">#</span>
&lt;<span style="color: #000000; font-weight:bold;">filesmatch</span> <span style="color: #7f007f;">&quot;<span style="color: #000099; font-weight: bold;">\.</span>(css|js|jpg|jpeg|png|gif|htc)$&quot;</span>&gt;
    <span style="color: #00007f;">ExpiresActive</span> <span style="color: #0000ff;">On</span>
    <span style="color: #00007f;">ExpiresDefault</span> <span style="color: #7f007f;">&quot;access plus 5 years&quot;</span>
&lt;/<span style="color: #000000; font-weight:bold;">filesmatch</span>&gt;</pre></div></div>

<p>Note: You&#8217;ll need the <strong>rewrite</strong> and <strong>expires</strong> modules enabled in Apache. This is for Apache 2. The syntax above may be somewhat different for Apache 1.3. To enable the modules in Apache 2 you can simply use:</p>

<div class="wp_syntax"><div class="code"><pre class="bash" style="font-family:monospace;">a2enmod rewrite
a2enmod expires</pre></div></div>

<p>Done! Now, whenever the site is deployed, only changed assets will be downloaded. Fast, efficient and headache free. Well, unless&#8230;</p>
<h3>Caveats</h3>
<p>The above script is purely to illustrate the process. Your specific needs may well need a slightly different approach. For example, there may be other areas it needs to look for URLs. If you do a lot of dynamic construction of URLs or funky script includes with JavaScript, you may need a secondary deployment script or procedure in order to accommodate such features. Using this technique, you must be careful to add the unique version to <strong>all</strong> the file types looked for in the deployment script, otherwise you&#8217;re telling the browser to cache a file indefinitely without the URL changing on new versions being deployed.</p>
<p>Another area to watch out for would be if you serve assets from different domains. Again, this technique will work in principle but will need some modification. It&#8217;s an exercise left to you, dear reader.</p>
<p>So, there you have it. A reasonably hassle free, efficient and optimised caching policy for your web applications. I hope you find this helpful &#8211; good luck.</p>
]]></content:encoded>
			<wfw:commentRss>http://2tap.com/2009/05/18/efficient-caching-of-versioned-javascript-css-and-image-assets-for-fun-and-profit/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>

