<?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>Ardamis &#187; Tutorials</title>
	<atom:link href="http://www.ardamis.com/category/tutorials/feed/" rel="self" type="application/rss+xml" />
	<link>http://www.ardamis.com</link>
	<description>Ardamis is a blog about web development and technology in general.</description>
	<lastBuildDate>Sat, 04 Feb 2012 15:26:27 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.3.1</generator>
		<item>
		<title>A cache-friendly method for reducing WordPress comment spam</title>
		<link>http://www.ardamis.com/2011/08/27/a-cache-proof-method-for-reducing-comment-spam/</link>
		<comments>http://www.ardamis.com/2011/08/27/a-cache-proof-method-for-reducing-comment-spam/#comments</comments>
		<pubDate>Sat, 27 Aug 2011 08:34:53 +0000</pubDate>
		<dc:creator>ardamis</dc:creator>
				<category><![CDATA[Nonsense]]></category>
		<category><![CDATA[Tutorials]]></category>
		<category><![CDATA[Web Site Dev]]></category>
		<category><![CDATA[WordPress]]></category>
		<category><![CDATA[application]]></category>
		<category><![CDATA[blogging]]></category>
		<category><![CDATA[coding]]></category>
		<category><![CDATA[comment spam]]></category>
		<category><![CDATA[database]]></category>
		<category><![CDATA[javascript]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[programming]]></category>
		<category><![CDATA[security]]></category>
		<category><![CDATA[spam]]></category>
		<category><![CDATA[templates]]></category>
		<category><![CDATA[themes]]></category>
		<category><![CDATA[web app]]></category>
		<category><![CDATA[xhtml]]></category>

		<guid isPermaLink="false">http://www.ardamis.com/?p=1673</guid>
		<description><![CDATA[A safe-for-cached-pages method of filtering out spam comments by requiring at least some time to have passed between the time the page is loaded and the form is submitted.]]></description>
			<content:encoded><![CDATA[<p>In the endless battle against WordPress comment spam, I&#8217;ve developed and then refined a few different methods for preventing spam from getting to the database to begin with.  My philosophy has always been that a human visitor and a spam bot behave differently (after all, we&#8217;re not dealing with <a href="http://en.wikipedia.org/wiki/Do_Androids_Dream_of_Electric_Sheep%3F#Androids">Nexus-6 model androids</a> here), and an effective spam-prevention method should be able to recognize the differences.  I also have a dislike for CAPTCHA methods that require a human visitor to <em>prove</em>, via an intentionally difficult test, that they aren&#8217;t a bot.  The ideal method, I feel, would be invisible to a human visitor, but still accurately identify comments submitted by bots.</p>
<h2>A history of spam fighting</h2>
<p>The most successful and simple method I found was a server-side system for <a href="http://www.ardamis.com/2007/12/15/using-timestamps-to-reduce-wordpress-comment-spam/">reducing comment spam by using a handshake method involving timestamps</a> on hidden form fields.  The general idea was that a bot would submit a comment more quickly than a human visitor, so if the comment was submitted too soon after the page was loaded, it was rejected.  A human caught in this trap would be able to click the Back button on the browser to resubmit.  This had proven to be very effective on ardamis.com, cutting the number of <a href="http://www.ardamis.com/2010/08/09/reducing-wordpress-spam-comments/">spam comments intercepted by Akismet per day to nearly zero</a>.  For a long time, the only problem was that it required modifying a core WordPress file, <strong>wp-comments-post.php</strong>.  Each time WordPress was updated, the core file was replaced.  If I didn&#8217;t then go back and make my modifications again, <a href="http://www.ardamis.com/2011/01/18/a-chart-illustrating-the-reduction-in-comment-spam-at-ardamis-com/">I would lose the spam protection</a> until I made the changes.  As it became easier to update WordPress (via the admin panel) and I updated it more frequently, editing the core file became more of a nuisance.</p>
<h2>A huge facepalm</h2>
<p>When Google began weighting page load times as part of its ranking algorithm, I implemented the <a href="http://wordpress.org/extend/plugins/wp-super-cache/" title="WP Super Cache">WP Super Cache</a> caching plugin on ardamis.com and configured it to use .htaccess and mod_rewrite to serve cache files.  Page load times certainly decreased, but the amount of spam detected by Akismet increased.  After a while, I realized that this was because the spam bots were submitting comments from static, cached pages, and the timestamps on those pages, which had been generated server-side with PHP, were already minutes old when the page was requested.  The form processing script, which normally rejects comments that are submitted too quickly to be written by a human visitor, happily accepted the timestamps.  Even worse, a second function of my anti-spam method also rejected comments that were submitted 10 minutes or more after the page was loaded.  Of course, most of the visitors were being served cached pages that were already more than 10 minutes old, so even legitimate comments were being rejected.  Using PHP to generate my timestamps obviously was not going to work if I wanted to keep serving cached pages.</p>
<h2>JavaScript to the rescue</h2>
<p>Generating real-time timestamps on cached pages requires JavaScript.  But instead of a reliable server clock setting the timestamp, the time is coming from the visitor&#8217;s system, which can&#8217;t be trusted to be accurate.  Merely changing the comment form to use JavaScript to generate the first timestamp wouldn&#8217;t work, because verifying a timestamp generated on the client-side against one generated with a server-side language would be disastrous.</p>
<p>Replacing the PHP-generated timestamps with JavaScript-generated timestamps would require substantial changes to the system.</p>
<p>Traditional client-side form validation using JavaScript happens when the form is submitted.  If the validation fails, the form is not submitted, and the visitor typically gets an alert with suggestions on how to make the form acceptable.  If the validation passes, the form submission continues without bothering the visitor.  To get our two timestamps, we can generate a first timestamp when the page loads and compare it to a second timestamp generated when the form is submitted.  If the visitor submits the form too quickly, we can display an alert showing the number of seconds remaining until the form can be successfully submitted.  This should hopefully be invisible to most visitors who choose to leave comments, but at the very least, far less irritating than a CAPTCHA system.</p>
<p>It took me two tries to get it right, but I&#8217;m going to discuss the less successful method first to point out its flaws.</p>
<h3>Method One (not good enough)</h3>
<p>Here&#8217;s how the original system flowed.</p>
<ol>
<li>Generate a first JS timestamp when the page is loaded.</li>
<li>Generate a second JS timestamp when the form is submitted.</li>
<li>Before the form is submitted, compare the two, and if enough time has passed, write a pre-determined passcode to a hidden INPUT element, then submit the form.</li>
<li>On the form processing page, use server-side logic to verify that the passcode is present and valid.</li>
</ol>
<p>The problem was that it seemed that certain bots could parse JavaScript enough to drop the pre-determined passcode into the hidden form field before submitting the form, circumventing the timestamps completely and defeating the system.  </p>
<p>It also failed to adhere to one of the basic tenants of form validation &#8211; that the input must be checked on both the client-side and the server-side.</p>
<h3>Method Two (better)</h3>
<p>Rather than having the server-side validation be merely a check to confirm that the passcode is present, method two goes back to comparing the timestamps a second time on the server side. Instead of a single hidden input, we now have two &#8211; one for each timestamp.  This is intended to prevent a bot from figuring out the ultimate validation mechanism by simply parsing the JavaScript.  Finally, the hidden fields are added to the form via jQuery, which makes it easier to implement and may act as another layer of obfuscation. </p>
<ol>
<li>Generate a first JS timestamp when the page is loaded and write it to a hidden form field.</li>
<li>Generate a second JS timestamp when the form is submitted and write it to a hidden form field.</li>
<li>Before the form is submitted, compare the two, and if enough time has passed, submit the form (client-side validation).</li>
<li>On the form processing page, use server-side logic to compare the timestamps a second time (server-side validation).</li>
</ol>
<p>The timestamp handshake works more like it did in the server-side-only method.  We still have to pass something from the comment form to the processing script, but it&#8217;s not too obvious from the HTML what is being done with it.</p>
<h2>The same downside plagues me</h2>
<p>Unfortunately, if we want to have any server-side validation at all, and we do, the core file <strong>wp-comments-post.php</strong> will still have to be modified.  In my experience, the system is not sufficiently effective using just client-side validation.  </p>
<h2>The code</h2>
<p>Two files must be modified to implement the validation.</p>
<p><strong>File 1: The theme&#8217;s comments.php file (older themes) or wp-includes\comment-template.php (newer themes)</strong></p>
<p>Your comment form lives somewhere.  My theme is based on Kubrick, the old default WordPress theme, and my comment form is in my theme folder, in a file named <strong>comments.php</strong>.  If your theme is newer and based on the current default theme, twentyeleven, the form is in <strong>wp-includes\comment-template.php</strong>.  If your theme isn&#8217;t based on either of these, all bets are off.  I know it&#8217;s confusing.  Sorry.</p>
<p>Add the JavaScript that creates and populates the timestamp fields.  Be sure to confirm that your comment form has an ID of <strong>commentform</strong>.  I&#8217;m using jQuery to help fire functions when the page loads.</p>
<pre class="brush: jscript; title: ; notranslate">
&lt;script type=&quot;text/javascript&quot; src=&quot;http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js&quot;&gt;&lt;/script&gt;
&lt;script type=&quot;text/javascript&quot;&gt;
$(document).ready(function(){
	ardGenTS1();
});

function ardGenTS1() {
	// prepare the form
	$('#commentform').append('&lt;input type=&quot;hidden&quot; name=&quot;ardTS1&quot; id=&quot;ardTS1&quot; value=&quot;1&quot; /&gt;');
	$('#commentform').append('&lt;input type=&quot;hidden&quot; name=&quot;ardTS2&quot; id=&quot;ardTS2&quot; value=&quot;1&quot; /&gt;');
	$('#commentform').attr('onsubmit', 'return validate()');
	// set a first timestamp when the page loads
	var ardTS1 = (new Date).getTime();
	document.getElementById(&quot;ardTS1&quot;).value = ardTS1;
}

function validate() {
	// read the first timestamp
	var ardTS1 = document.getElementById(&quot;ardTS1&quot;).value;
//	alert ('ardTS1: ' + ardTS1);
	// generate the second timestamp
	var ardTS2 = (new Date).getTime();
	document.getElementById(&quot;ardTS2&quot;).value = ardTS2;
//	alert ('ardTS2: ' + document.getElementById(&quot;ardTS2&quot;).value);
	// find the difference
	var diff = ardTS2 - ardTS1;
	var elapsed = Math.round(diff / 1000);
	var remaining = 10 - elapsed;
//	alert ('diff: ' + diff + '\n\nelapsed:' + elapsed);
	// check whether enough time has elapsed
	if (diff &gt; 10000) {
		// submit the form
		return true;
	}else{
		// display an alert if the form is submitted within 10 seconds
		alert(&quot;This site is protected by an anti-spam feature that requires 10 seconds to have elapsed between the page load and the form submission.\n\nPlease close this alert window.  The form may be resubmitted successfully in &quot; + remaining + &quot; seconds.&quot;);
		// prevent the form from being submitted
		return false;
	}
}
&lt;/script&gt;
</pre>
<p><strong>File 2: The wp-comments-post.php file</strong></p>
<p>The wp-comments-post.php file lives in the root of WordPress and handles the form processing.  We need to add a few lines that check the contents of our new validation input field.</p>
<p>Somewhere after line 53 or so (where <em>$comment_content</em> is defined), insert the following code.</p>
<pre class="brush: php; title: ; notranslate">
$ardTS1 = ( isset($_POST['ardTS1']) ) ? trim($_POST['ardTS1']) : 1;
$ardTS2 = ( isset($_POST['ardTS2']) ) ? trim($_POST['ardTS2']) : 2;
$ardTS = $ardTS2 - $ardTS1;

if ( $ardTS &lt; 10000 ) {
// If the difference of the timestamps is not more than 10 seconds, exit
    wp_die( __('&lt;strong&gt;ERROR&lt;/strong&gt;:  This site uses JavaScript validation to reduce comment spam.  Either your browser has JavaScript disabled, or the comment was not legitimately submitted.') );
}
</pre>
<p>That&#8217;s it.  Not so bad, right?</p>
<h2>Final thoughts</h2>
<p>One advantage to this method over the old PHP-only method is that even if the core file is replaced and the server-side validation is lost, the client-side validation continues to work, perhaps providing some measure of protection.</p>
<p>The method is safe for use with caching systems that create purely static, HTML pages.  I&#8217;ll follow up later and report on how effective it seems to be at stopping spam comments before they get to Akismet (and into the WordPress database).</p>
<p>Now, for a little extra protection, you can rename the <strong>wp-comments-post.php</strong> file and change the path in the comment form&#8217;s action attribute.  I&#8217;ve <a href="http://www.ardamis.com/2010/08/09/reducing-wordpress-spam-comments/">posted logs</a> showing that some bots just try to post spam directly to the <strong>wp-comments-post.php</strong> file, so renaming that file is an easy way to cut down on spam.  Just remember to come back and delete the <strong>wp-comments-post.php</strong> file each time you update WordPress.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.ardamis.com/2011/08/27/a-cache-proof-method-for-reducing-comment-spam/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Fixed: XAMPP Component Status Check failure [3].</title>
		<link>http://www.ardamis.com/2011/07/10/xampp-component-status-check-failure-3/</link>
		<comments>http://www.ardamis.com/2011/07/10/xampp-component-status-check-failure-3/#comments</comments>
		<pubDate>Sun, 10 Jul 2011 18:35:21 +0000</pubDate>
		<dc:creator>ardamis</dc:creator>
				<category><![CDATA[Microsoft]]></category>
		<category><![CDATA[Tutorials]]></category>
		<category><![CDATA[Web Site Dev]]></category>
		<category><![CDATA[mysql]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[troubleshooting]]></category>
		<category><![CDATA[Windows 7]]></category>
		<category><![CDATA[XAMPP]]></category>

		<guid isPermaLink="false">http://www.ardamis.com/?p=1603</guid>
		<description><![CDATA[A simple fix for the XAMPP Component Status Check failure [3]. error when launching the XAMPP 1.7.4 Control Panel.]]></description>
			<content:encoded><![CDATA[<p>I&#8217;m running XAMPP 1.7.4 [PHP: 5.3.5] (not as a service) on 64-bit Windows 7 Professional.  </p>
<p>I installed XAMPP to E:\xampp, and I have pinned the XAMPP Control Panel (xampp-control.exe) to the taskbar for easier access, but starting up xampp-control.exe from that shortcut throws an error:</p>
<blockquote><p><strong>XAMPP Control</strong></p>
<p>XAMPP Component Status Check failure [3].</p>
<p>Current directory: E:\xampp</p>
<p>Run this program only from your XAMPP root directory.</p>
<p>[OK] [Cancel]</p></blockquote>
<p>Strangely enough, I even get this error even when running xampp-control.exe from my XAMPP root directory, which really is E:\xampp.</p>
<p>The last post in the thread at <a href="http://www.apachefriends.org/f/viewtopic.php?f=16&#038;t=44320&#038;sid=a41029c6a36bbf5b3bb5817f37842340&#038;start=60">http://www.apachefriends.org/f/viewtopic.php?f=16&#038;t=44320&#038;sid=a41029c6a36bbf5b3bb5817f37842340&#038;start=60</a> offers a simple solution:  change the Install_Dir value under HKEY_LOCAL_MACHINE to point to C:\xampp.  According to the thread, the error message is due to a bug where the Install_Dir is checked against a hard-coded path on C:\.  That may or may not be the case, but the suggested work-around seems to be effective.</p>
<p>Here&#8217;s a registry merge for Windows 7 64-bit that will make the change for you.</p>
<pre class="brush: plain; title: ; notranslate">
Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\xampp]
&quot;Install_Dir&quot;=&quot;C:\\xampp&quot;
</pre>
<p>Now xampp-control.exe launches without the error, and I haven&#8217;t noticed anything (PHP, MySQL, etc.) not working because of the bogus path.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.ardamis.com/2011/07/10/xampp-component-status-check-failure-3/feed/</wfw:commentRss>
		<slash:comments>16</slash:comments>
		</item>
		<item>
		<title>How to fix the &#8220;PHP Fatal error:  Call to undefined function  get_header()&#8221; error in WordPress</title>
		<link>http://www.ardamis.com/2011/06/02/fix-for-php-fatal-error-get_header-in-wordpress/</link>
		<comments>http://www.ardamis.com/2011/06/02/fix-for-php-fatal-error-get_header-in-wordpress/#comments</comments>
		<pubDate>Thu, 02 Jun 2011 17:07:13 +0000</pubDate>
		<dc:creator>ardamis</dc:creator>
				<category><![CDATA[Tutorials]]></category>
		<category><![CDATA[Web Site Dev]]></category>
		<category><![CDATA[WordPress]]></category>
		<category><![CDATA[500 error]]></category>
		<category><![CDATA[Apache]]></category>
		<category><![CDATA[blogging]]></category>
		<category><![CDATA[coding]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[programming]]></category>
		<category><![CDATA[security]]></category>
		<category><![CDATA[themes]]></category>
		<category><![CDATA[troubleshooting]]></category>

		<guid isPermaLink="false">http://www.ardamis.com/?p=1430</guid>
		<description><![CDATA[Fix the "PHP Fatal error: Call to undefined function get_header()" error in WordPress.]]></description>
			<content:encoded><![CDATA[<p>While making changes to my WordPress theme, I noticed that the error_log file in my theme folder contained dozens of PHP Fatal error lines:</p>
<pre class="brush: plain; title: ; notranslate">
...
[01-Jun-2011 14:25:15] PHP Fatal error:  Call to undefined function  get_header() in /home/accountname/public_html/ardamis.com/wp-content/themes/ars/index.php on line 7
[01-Jun-2011 20:58:23] PHP Fatal error:  Call to undefined function  get_header() in /home/accountname/public_html/ardamis.com/wp-content/themes/ars/index.php on line 7
...
</pre>
<p>The first seven lines of my theme&#8217;s index.php file:</p>
<pre class="brush: php; title: ; notranslate">
&lt;?php ini_set('display_errors', 0); ?&gt;
&lt;?php
/**
 * @package WordPress
 * @subpackage Ars_Theme
*/
get_header(); ?&gt;
</pre>
<p>I realized that the error was being generated each time that my theme&#8217;s index.php file was called directly, and that the error was caused by the theme&#8217;s inability to locate the WordPress <strong>get_header</strong> function (which is completely normal).  Thankfully, the descriptive error wasn&#8217;t being output to the browser, but was only being logged to the error_log file, due to the inclusion of the <strong>ini_set(&#8216;display_errors&#8217;, 0);</strong> line.  I had learned this the hard way a few months ago when I found that calling the theme&#8217;s index.php file directly would generate an error message, output to the browser, that would reveal my hosting account username as part of the absolute path to the file throwing the error.</p>
<p>I decided the best way to handle this would be to check to see if the file could find the <strong>get_header</strong> function, and if it could not, simply redirect the visitor to the site&#8217;s home page.  The code I used to do this:</p>
<pre class="brush: php; title: ; notranslate">
&lt;?php ini_set('display_errors', 0); ?&gt;
&lt;?php
/**
* @package WordPress
* @subpackage Ars_Theme
*/
if (function_exists('get_header')) {
	get_header();
}else{
    /* Redirect browser */
    header(&quot;Location: http://&quot; . $_SERVER['HTTP_HOST'] . &quot;&quot;);
    /* Make sure that code below does not get executed when we redirect. */
    exit;
}; ?&gt;
</pre>
<p>So there you have it.  No more fatal errors due to <strong>get_header</strong> when loading the WordPress theme&#8217;s index.php file directly.  And if something else in the file should throw an error, <strong>ini_set(&#8216;display_errors&#8217;, 0);</strong> means it still won&#8217;t be sent to the browser.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.ardamis.com/2011/06/02/fix-for-php-fatal-error-get_header-in-wordpress/feed/</wfw:commentRss>
		<slash:comments>7</slash:comments>
		</item>
		<item>
		<title>Online tools for detecting malware on web sites</title>
		<link>http://www.ardamis.com/2011/05/07/detecting-malware-on-web-sites/</link>
		<comments>http://www.ardamis.com/2011/05/07/detecting-malware-on-web-sites/#comments</comments>
		<pubDate>Sat, 07 May 2011 18:48:22 +0000</pubDate>
		<dc:creator>ardamis</dc:creator>
				<category><![CDATA[Nonsense]]></category>
		<category><![CDATA[Tutorials]]></category>
		<category><![CDATA[Web Site Dev]]></category>
		<category><![CDATA[WordPress]]></category>
		<category><![CDATA[Apache]]></category>
		<category><![CDATA[blogging]]></category>
		<category><![CDATA[coding]]></category>
		<category><![CDATA[htaccess]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[programming]]></category>
		<category><![CDATA[security]]></category>

		<guid isPermaLink="false">http://www.ardamis.com/?p=1275</guid>
		<description><![CDATA[Online tools for detecting infections on web sites and for identifying vulnerabilites.]]></description>
			<content:encoded><![CDATA[<p>Just a few notes to myself about monitoring web sites for infections/malware and potential vulnerabilities.</p>
<h2>Tools for detecting infections on web sites</h2>
<h3>Google Webmaster Tools</h3>
<p>Your first stop should be here, as I&#8217;ve personally witnessed alerts show up in Webmaster Tools, even when all the following tools gave the site a passing grade.  If your site is registered here, and Google finds weird pages on your site, an alert will appear.  You can also have the messages forwarded to your email account on file, by choosing the Forward option under the All Messages area of the Home page.</p>
<div id="attachment_1382" class="wp-caption aligncenter" style="width: 784px"><a href="http://www.ardamis.com/wp-content/uploads/2011/05/google-webmaster-tools-hack-alert.png"><img src="http://www.ardamis.com/wp-content/uploads/2011/05/google-webmaster-tools-hack-alert.png" alt="" title="google-webmaster-tools-hack-alert" width="774" height="435" class="size-full wp-image-1382" /></a><p class="wp-caption-text">Google Webmaster Tools Hack Alert</p></div>
<h3>Google Safe Browsing</h3>
<p>The Google Safe Browsing report for ardamis.com: <a href="http://safebrowsing.clients.google.com/safebrowsing/diagnostic?site=ardamis.com">http://safebrowsing.clients.google.com/safebrowsing/diagnostic?site=ardamis.com</a></p>
<h3>Norton Safe Web</h3>
<p><a href="https://safeweb.norton.com/">https://safeweb.norton.com/</a></p>
<p>The Norton Safe Web report for ardamis.com: <a href="https://safeweb.norton.com/report/show?url=ardamis.com">https://safeweb.norton.com/report/show?url=ardamis.com</a></p>
<h2>Tools for analyzing a site for vulnerabilities</h2>
<h3>Sucuri Site Check</h3>
<p><a href="http://sitecheck.sucuri.net/scanner/">http://sitecheck.sucuri.net/scanner/</a></p>
<p>The Sucuri report for ardamis.com: <a href="http://sitecheck.sucuri.net/scanner/?scan=www.ardamis.com">http://sitecheck.sucuri.net/scanner/?scan=www.ardamis.com</a>.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.ardamis.com/2011/05/07/detecting-malware-on-web-sites/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Online tools for testing web page performance</title>
		<link>http://www.ardamis.com/2011/04/10/online-tools-for-testing-web-page-performance/</link>
		<comments>http://www.ardamis.com/2011/04/10/online-tools-for-testing-web-page-performance/#comments</comments>
		<pubDate>Mon, 11 Apr 2011 02:49:53 +0000</pubDate>
		<dc:creator>ardamis</dc:creator>
				<category><![CDATA[Tutorials]]></category>
		<category><![CDATA[Web Site Dev]]></category>
		<category><![CDATA[blogging]]></category>
		<category><![CDATA[coding]]></category>
		<category><![CDATA[database]]></category>
		<category><![CDATA[javascript]]></category>
		<category><![CDATA[mysql]]></category>
		<category><![CDATA[network]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[programming]]></category>
		<category><![CDATA[web app]]></category>

		<guid isPermaLink="false">http://www.ardamis.com/?p=816</guid>
		<description><![CDATA[A collection of online tools for measuring the performance of web pages, including time to first byte.]]></description>
			<content:encoded><![CDATA[<p>I&#8217;ve written a few tutorials lately on how to reduce page load times.  While I use Google&#8217;s Page Speed Firefox/Firebug plugin for evaluating pages for load times, there are times when I want a second opinion, or want to point a client to a tool.  This post is a collection of links to online tools for testing web page performance.</p>
<h2>Page Speed Online</h2>
<p><a href="http://pagespeed.googlelabs.com/">http://pagespeed.googlelabs.com/</a></p>
<p>Google&#8217;s wonderful Page Speed tool, once only available as a Firefox browser Add-on, finally arrives as an online tool.  Achieving a high score (ardamis.com is a 96/100) should be on every web developer&#8217;s list of things to do before the culmination of a project.</p>
<p>Enter a URL and Page Speed Online will run performance tests based on a set of best practices known to reduce page load times.</p>
<p>Optimizing caching &#8211; keeping your application&#8217;s data and logic off the network altogether<br />
Minimizing round-trip times &#8211; reducing the number of serial request-response cycles<br />
Minimizing request overhead &#8211; reducing upload size<br />
Minimizing payload size &#8211; reducing the size of responses, downloads, and cached pages<br />
Optimizing browser rendering &#8211; improving the browser&#8217;s layout of a page</p>
<h2>WebPagetest</h2>
<p><a href="http://www.webpagetest.org/">http://www.webpagetest.org/</a></p>
<p>WebPagetest is an excellent application for users who want the same sort of detailed reporting that one gets with Page Speed.</p>
<p>Load time speed test on first view (cold cache) and repeat view (hot cache), first byte and start render<br />
Optimization checklist<br />
Enable keep-alive, HTML compression, image compression, cache static content, combine JavaScript and CSS, and use of CDN<br />
Waterfall<br />
Response headers for each request</p>
<h2>Load Impact</h2>
<p><a href="http://loadimpact.com/pageanalyzer.php">http://loadimpact.com/pageanalyzer.php</a></p>
<p>Load Impact is an online load testing service that lets you load- and stress test your website over the Internet.  The page analyzer analyzes your web page performance by emulating how a web browser would load your page and all resources referenced in it. The page and its referenced resources are loaded and important performance metrics are measured and displayed in a load-bar diagram along with other per-resource attributes such as URL, size, compression ratio and HTTP status code.</p>
<h2>ByteCheck</h2>
<p><a href="http://www.bytecheck.com/">http://www.bytecheck.com/</a></p>
<p>ByteCheck is a super minimal site that return your page&#8217;s all-important time to first byte (TTFB). Time to first byte is the time it takes for a browser to start receiving information after it has started to make the request to the server, and is responsible for a visitor&#8217;s first impression that a page is fast- or slow-loading.</p>
<h2>Web Page Analyzer</h2>
<p><a href="http://websiteoptimization.com/services/analyze/">http://websiteoptimization.com/services/analyze/</a></p>
<p>My opinion is that the Web Page Analyzer report is good for beginners without much technical knowledge of things like gzip compression and Expires headers.  It&#8217;s a bit dated, and is primarily concerned with basics like how many images a page contains.  It tells you how fast you can expect your page to load for dial-up visitors, which strikes me as quaint and not particularly useful.</p>
<p>Total HTTP requests<br />
Total size<br />
Total size per object type (CSS, JavaScript, images, etc.)<br />
Analysis of number of files and file size as compared to recommended limits.</p>
<h2>The Performance Grader</h2>
<p><a href="http://www.joomlaperformance.com/component/option,com_performance/Itemid,52/">http://www.joomlaperformance.com/component/option,com_performance/Itemid,52/</a></p>
<p>This is another simplistic analysis of a site, like Web Page Analyzer, that returns its analysis in the form of pass/fail grades on about 14 different tests.  I expect that it would be useful for developers who want to show a client a third-party&#8217;s analysis of their work, if the third-party is not terribly technically savvy.  </p>
<p>One unique thing about this tool, though, is that it totals up the size of all images referenced in CSS files (even those that the current page isn&#8217;t using).</p>
<p>HTML Size<br />
Total Size<br />
Total Requests<br />
Generation Time<br />
Number of Hosts<br />
Number of Images<br />
Size of Images<br />
Number of CSS Files<br />
Size of CSS Files<br />
Number of Script Files<br />
Size of Script Files<br />
HTML Encoding<br />
Valid HTML<br />
Frames</p>
]]></content:encoded>
			<wfw:commentRss>http://www.ardamis.com/2011/04/10/online-tools-for-testing-web-page-performance/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>How to remove the GRUB bootloader from Windows XP</title>
		<link>http://www.ardamis.com/2010/12/06/how-to-remove-the-grub-bootloader-from-windows-xp/</link>
		<comments>http://www.ardamis.com/2010/12/06/how-to-remove-the-grub-bootloader-from-windows-xp/#comments</comments>
		<pubDate>Mon, 06 Dec 2010 19:14:14 +0000</pubDate>
		<dc:creator>ardamis</dc:creator>
				<category><![CDATA[Hardware]]></category>
		<category><![CDATA[Microsoft]]></category>
		<category><![CDATA[Tutorials]]></category>
		<category><![CDATA[troubleshooting]]></category>

		<guid isPermaLink="false">http://www.ardamis.com/?p=1145</guid>
		<description><![CDATA[How to replace the boot sector and MBR of Windows XP to remove the GRUB bootloader.]]></description>
			<content:encoded><![CDATA[<p>I had set up a hard drive with two Windows XP installations on separate partitions and used GRUB to choose between them at boot.  Eventually, I needed only one of these installations and wanted to clone/copy it to a separate drive.  I happened to have an old copy of Ghost 2003, so I used that to clone the partition I wanted to keep.</p>
<p>But when I tried to boot that install, all I got was the word GRUB on an otherwise blank screen after the POST.</p>
<p>I did some Googling and found the <a href="http://www.ntcompatible.com/How_to_remove_GRUB_loader_t28242.html">How to remove GRUB loader!?</a> post at <a href="http://www.ntcompatible.com">ntcompatible.com</a>.</p>
<p>Basically, you can get around this problem by replacing the boot sector and MBR.</p>
<ol>
<li>Boot into Recovery Console with the XP install media by choosing the Repair option</li>
<li>Choose the installation to work on</li>
<li>At the command prompt (assuming your installation is on C:), enter: <strong>fixboot c:</strong></li>
<li>Proceed through any warnings
<li>At the command prompt, enter: <strong>map</strong></li>
<li>Record the name of the device on which you will be writing the new master boot record</li>
<li>At the command prompt, enter: <strong>fixmbr <em>[device_name]</em></strong> (where the device name is something like <em>\Device\HardDisk0</em></li>
<li>Proceed through any warnings</li>
<li>Exit Recovery Console and reboot</li>
</ol>
<p>Resources: <a href="http://www.microsoft.com/resources/documentation/windows/xp/all/proddocs/en-us/bootcons_fixboot.mspx?mfr=true">Windows XP Professional Product Documentation &#8211; fixboot</a> and <a href="http://www.microsoft.com/resources/documentation/windows/xp/all/proddocs/en-us/bootcons_fixmbr.mspx?mfr=true">Windows XP Professional Product Documentation &#8211; fixmbr</a></p>
]]></content:encoded>
			<wfw:commentRss>http://www.ardamis.com/2010/12/06/how-to-remove-the-grub-bootloader-from-windows-xp/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>A macro for toggling the read-only attribute of Word.qat</title>
		<link>http://www.ardamis.com/2010/11/02/a-macro-for-toggling-the-read-only-attribute-of-word-qat/</link>
		<comments>http://www.ardamis.com/2010/11/02/a-macro-for-toggling-the-read-only-attribute-of-word-qat/#comments</comments>
		<pubDate>Tue, 02 Nov 2010 15:01:55 +0000</pubDate>
		<dc:creator>ardamis</dc:creator>
				<category><![CDATA[Microsoft]]></category>
		<category><![CDATA[Tutorials]]></category>
		<category><![CDATA[application]]></category>
		<category><![CDATA[Office]]></category>
		<category><![CDATA[troubleshooting]]></category>

		<guid isPermaLink="false">http://www.ardamis.com/?p=1116</guid>
		<description><![CDATA[Protect the Word QAT from unintended changes by setting the read-only attribute on Word.qat from within Word itself.]]></description>
			<content:encoded><![CDATA[<p>Actually, my method of toggling the read-only attribute of Word.qat requires two macros.  One switches on the read-only attribute of Word.qat to prevent it from being changed.  The other clears the read-only attribute so buttons can be added or removed.</p>
<p>Just add these macros as buttons to the QAT to quickly protect and unprotect it.  I prefer two buttons to a single button because I don&#8217;t know of a way of telling, visually, the current state of the read-only attribute of a file.  I&#8217;d love to change the appearance of the button to indicate the state, but I haven&#8217;t found a way to do this.  So for now, two buttons allow the user to take exactly the action desired.</p>
<p>Why would you ever need to do this?</p>
<p>Buttons disappearing from the QAT is a pretty common occurrence.</p>
<p>If your Quick Access Toolbar contains buttons from templates or COM add-ins, these custom buttons can be lost when Word is closed and reopened.  To demonstrate this, add such a button to the QAT, then close Word and reopen it from the command line with <strong>winword.exe /a</strong> (the <strong>/n</strong> switch may also demonstrate this).  Word will open, but without any add-ins.  Instead of creating a temporary Word.qat with the default buttons, the working Word.qat file is edited to remove all the non-native Word buttons.  The appearance to the user is that Word loses the custom buttons.  Once the buttons disappear, they do not return when Word is opened normally.</p>
<p>From what I can tell, the QAT buttons disappearing isn&#8217;t a random event or a bug, but an intentional consequence of initiating a Word instance without any add-ins or a result of a badly written function in a template or add-in.</p>
<h3>The LockQAT Macro</h3>
<pre class="brush: plain; title: ; notranslate">
Sub LockQAT()
'
' LockQAT Macro
'
'

    Dim appdata, thepath, objFSO, objFile

    Set oShell = CreateObject(&quot;WScript.Shell&quot;)
    appdata = oShell.ExpandEnvironmentStrings(&quot;%APPDATA%&quot;)
    thepath = appdata &amp; &quot;\Microsoft\Office\Word.qat&quot;

    Set objFSO = CreateObject(&quot;Scripting.FileSystemObject&quot;)
    Set objFile = objFSO.GetFile(thepath)

    'Determine if the file is ALREADY Read-Only
    If objFile.Attributes And 1 Then
        MsgBox &quot;The Word.QAT file is already Read-only.&quot;
    Else
        MsgBox &quot;Locking the QAT from further editing.&quot;
        objFile.Attributes = objFile.Attributes + 1
    End If

' Resources
' http://www.4guysfromrolla.com/webtech/112600-1.shtml

End Sub
</pre>
<h3>The UnlockQAT Macro</h3>
<pre class="brush: plain; title: ; notranslate">
Sub UnlockQAT()
'
' UnlockQAT Macro
'
'

    Dim appdata, thepath, objFSO, objFile

    Set oShell = CreateObject(&quot;WScript.Shell&quot;)
    appdata = oShell.ExpandEnvironmentStrings(&quot;%APPDATA%&quot;)
    thepath = appdata &amp; &quot;\Microsoft\Office\Word.qat&quot;

    Set objFSO = CreateObject(&quot;Scripting.FileSystemObject&quot;)
    Set objFile = objFSO.GetFile(thepath)

    'Determine if the file is ALREADY Read-Only
    If objFile.Attributes And 1 Then
        MsgBox &quot;Unlocking the QAT for editing.&quot;
        objFile.Attributes = objFile.Attributes - 1
    Else
        MsgBox &quot;The Word.QAT file is already writeable.&quot;
    End If

' Resources
' http://www.4guysfromrolla.com/webtech/112600-1.shtml

End Sub
</pre>
]]></content:encoded>
			<wfw:commentRss>http://www.ardamis.com/2010/11/02/a-macro-for-toggling-the-read-only-attribute-of-word-qat/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>How to clean up those Mac OSX hidden files</title>
		<link>http://www.ardamis.com/2010/08/10/clean-up-those-mac-osx-hidden-files/</link>
		<comments>http://www.ardamis.com/2010/08/10/clean-up-those-mac-osx-hidden-files/#comments</comments>
		<pubDate>Tue, 10 Aug 2010 06:20:19 +0000</pubDate>
		<dc:creator>ardamis</dc:creator>
				<category><![CDATA[Microsoft]]></category>
		<category><![CDATA[Nonsense]]></category>
		<category><![CDATA[Tutorials]]></category>
		<category><![CDATA[Apple]]></category>
		<category><![CDATA[coding]]></category>
		<category><![CDATA[Windows 7]]></category>

		<guid isPermaLink="false">http://www.ardamis.com/?p=941</guid>
		<description><![CDATA[How to delete the hidden files that Mac OSX puts on flash drives, USB hard drives, and other shared storage.]]></description>
			<content:encoded><![CDATA[<p>I&#8217;ve written a simple batch file to remove those hidden files that Mac OSX leaves all over shared drives to annoy us Windows users.</p>
<pre class="brush: php; title: ; notranslate">
del /s /a:h ._*
:: http://en.wikipedia.org/wiki/Resource_fork

del /s /a:h .DS_Store
:: http://en.wikipedia.org/wiki/.DS_Store

del /s /a:h .Trashes
:: http://en.wikipedia.org/wiki/Recycle_bin_(computing)

@pause
</pre>
<h2>How to use</h2>
<p>Copy the code into a text file, rename it <strong>cleanOSX.bat</strong> and run it from the root of the drive you wish to clean.  The script will look through all subfolders, deleting any hidden file or folder with a name that begins with <strong>._</strong>, or that matches <strong>.DS_Store</strong> or <strong>.Trashes</strong>.  Depending on the number of these files on your drive, the process of deleting them could take some time.</p>
<h2>Where do these files come from?</h2>
<h3>The resource fork</h3>
<blockquote><p>The resource fork is a construct of the Mac OS operating system used to store structured data in a file, alongside unstructured data stored within the data fork. A resource fork stores information in a specific form, such as icons, the shapes of windows, definitions of menus and their contents, and application code (machine code). For example, a word processing file might store its text in the data fork, while storing any embedded images in the same file&#8217;s resource fork. The resource fork is used mostly by executables, but every file is able to have a resource fork.<br />
&#8230;<br />
Currently, Mac OS X does support resource forks on Windows SMB shares by creating a hidden file in the same directory with the data fork file, with the characters “._” at the beginning of the file name. However, this may be annoying for some users, especially because some Windows power users always keep hidden files visible. Besides, Windows does not treat those files correctly as the file itself is moved or removed.</p>
<p><a href="http://en.wikipedia.org/wiki/Resource_fork">http://en.wikipedia.org/wiki/Resource_fork</a></p></blockquote>
<h3>The Desktop Services Store</h3>
<blockquote><p>.DS_Store (Desktop Services Store) is a hidden file created by Apple Inc.&#8217;s Mac OS X operating system to store custom attributes of a folder such as the position of icons or the choice of a background image.</p>
<p><a href="http://en.wikipedia.org/wiki/.DS_Store">http://en.wikipedia.org/wiki/.DS_Store</a>
</p></blockquote>
<h3>The Trash folder</h3>
<blockquote><p>Under Mac OS X, when a file is deleted in Finder, it is moved to a .Trashes folder, and when viewing the device&#8217;s available space the space occupied by the deleted files is shown as occupied.</p>
<p><a href="http://en.wikipedia.org/wiki/Recycle_bin_(computing)">http://en.wikipedia.org/wiki/Recycle_bin_(computing)</a>
</p></blockquote>
]]></content:encoded>
			<wfw:commentRss>http://www.ardamis.com/2010/08/10/clean-up-those-mac-osx-hidden-files/feed/</wfw:commentRss>
		<slash:comments>7</slash:comments>
		</item>
		<item>
		<title>Simple file backup with a batch file</title>
		<link>http://www.ardamis.com/2010/08/08/simple-file-backup-with-a-batch-file/</link>
		<comments>http://www.ardamis.com/2010/08/08/simple-file-backup-with-a-batch-file/#comments</comments>
		<pubDate>Sun, 08 Aug 2010 06:30:31 +0000</pubDate>
		<dc:creator>ardamis</dc:creator>
				<category><![CDATA[Microsoft]]></category>
		<category><![CDATA[Nonsense]]></category>
		<category><![CDATA[Tutorials]]></category>
		<category><![CDATA[application]]></category>
		<category><![CDATA[coding]]></category>
		<category><![CDATA[Windows 7]]></category>

		<guid isPermaLink="false">http://www.ardamis.com/?p=910</guid>
		<description><![CDATA[How to write a simple batch file for backing up files to a drive letter and reducing the risk of those files being copied to the wrong device.]]></description>
			<content:encoded><![CDATA[<p>I&#8217;ve written a simple batch file for backing up files and folder onto a different drive letter.  </p>
<p>In my case, the destination drive will be a USB drive.  Even though I&#8217;ve configured Windows to always assign the same drive letter to that device, the possibility remains that I&#8217;ll connect a different drive that will be assigned the same drive letter.  In order to be sure that I&#8217;m backing up to the correct drive, the batch file performs a few checks before copying files.</p>
<p>The first check confirms that a disk exists at that drive letter.  The second check confirms that the path is valid.  The third check looks for the presence of a file in the destination directory.</p>
<p>To use, simply paste the following code into a text file, change the variables to match your environment, add additional <strong>xcopy</strong> lines for other folders, then save it as a .bat file.  Fire the batch file manually, or place it in your startup folder to automatically back up your files each time you log in to Windows.</p>
<pre class="brush: plain; title: ; notranslate">
:: Back up select files and folders to a location that may be an external drive
@echo off

:: Set some variables
set destinationDrive=D:
set destinationPath=backup
Set destination=%destinationDrive%\%destinationPath%
set validationFile=asdf.txt

:: Check to see if the drive is available
if not exist %destinationDrive%\. goto :nodestinationDrive
:: Move to destination drive
%destinationDrive%

:: Check to see if the path is available
if not exist &quot;\%destinationPath%\.&quot; goto :nodestinationPath
:: Move to destination path
cd %destinationPath%

:: Check to see if the validation file exists
if not exist %validationFile% goto :novalidationFile

:: Backup location is valid
@echo The backup location &quot;%destination%&quot; is valid.

:: Copy files and folders if source is newer than destination

:: Desktop
@xcopy &quot;%USERPROFILE%\Desktop&quot; &quot;%destination%\Desktop&quot; /d /e /c /i /q /h /r /k /y

@echo.
@echo Files copied.  Please review output for errors.
@pause
goto eof

:nodestinationDrive
@echo The destination drive &quot;%destinationDrive%&quot; does not exist.
goto :nocopy

:nodestinationPath
@echo The destination path &quot;%destinationPath%&quot; does not exist on drive %destinationDrive%.
goto :nocopy

:novalidationFile
@echo The validation file does not exist.
goto :nocopy

:: No files have been copied
:nocopy
::@echo A valid backup location cannot be confirmed.
@echo No files have been copied.

@echo.
@pause
</pre>
<p>This file works with Windows XP through Windows 7.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.ardamis.com/2010/08/08/simple-file-backup-with-a-batch-file/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>How to send the correct headers to leverage browser caching</title>
		<link>http://www.ardamis.com/2010/07/17/sending-headers-to-leverage-browser-caching/</link>
		<comments>http://www.ardamis.com/2010/07/17/sending-headers-to-leverage-browser-caching/#comments</comments>
		<pubDate>Sat, 17 Jul 2010 23:23:39 +0000</pubDate>
		<dc:creator>ardamis</dc:creator>
				<category><![CDATA[Tutorials]]></category>
		<category><![CDATA[Web Site Dev]]></category>
		<category><![CDATA[Apache]]></category>
		<category><![CDATA[coding]]></category>
		<category><![CDATA[firefox]]></category>
		<category><![CDATA[godaddy]]></category>
		<category><![CDATA[htaccess]]></category>
		<category><![CDATA[javascript]]></category>

		<guid isPermaLink="false">http://www.ardamis.com/?p=794</guid>
		<description><![CDATA[How to use .htaccess to send the correct HTTP headers in order to leverage browser caching and reduce page load times for returning visitors.]]></description>
			<content:encoded><![CDATA[<p>As a follow-up to my post on <a href="http://www.ardamis.com/2010/07/11/compress-files-without-mod_gzip-or-mod_deflate/">compressing .php, .css and .js files without mod_gzip or mod_deflate</a>, I&#8217;m documenting the changes I made to the .htaccess file on ardamis.com in order to speed up page load times for returning visitors and satisfy the <a href="http://code.google.com/speed/page-speed/docs/caching.html#LeverageBrowserCaching">Leverage browser caching</a> recommendation of Google&#8217;s <a href="http://code.google.com/speed/page-speed/">Page Speed</a> Firefox/Firebug Add-on. </p>
<p>A great explanation of why browser caching helps the web deliver a better user experience is at <a href="http://betterexplained.com/articles/how-to-optimize-your-site-with-http-caching/">betterexplained.com</a>.</p>
<p>Two authoritative articles on the subject are Google&#8217;s <a href="http://code.google.com/speed/page-speed/docs/caching.html">Performance Best Practices | Optimize caching</a> and Yahoo&#8217;s <a href="http://developer.yahoo.com/performance/rules.html#expires">Best Practices for Speeding Up Your Web Site | Add an Expires or a Cache-Control Header</a>.</p>
<p>I&#8217;d like to point out that in researching browser cashing, I came across a lot of information that contradicted the rather clear instructions from Google:</p>
<blockquote><p>It is important to specify one of <code>Expires</code> or <code>Cache-Control max-age</code>, <i>and</i> one of <code>Last-Modified</code> or <code>ETag</code>, for all cacheable resources. It is redundant to specify both <code>Expires</code> and <code>Cache-Control: max-age</code>, or to specify both <code>Last-Modified</code> and <code>ETag</code>.</p></blockquote>
<p>I&#8217;m not sure that this recommendation is entirely correct, as the W3C states that Expires and Cache-Control max-age are used in different situations, with Cache-Control max-age overriding Expires in the event of conflicts.</p>
<blockquote><p>If a response includes both an Expires header and a max-age directive, the max-age directive overrides the Expires header, even if the Expires header is more restrictive. This rule allows an origin server to provide, for a given response, a longer expiration time to an HTTP/1.1 (or later) cache than to an HTTP/1.0 cache.</p>
<p><a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html">http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html</a></p></blockquote>
<p>It would seem that Cache-Control is the preferred method of controlling browser caching going forward.</p>
<blockquote><p>HTTP 1.1 clients will honour &#8220;Cache-Control&#8221; (which is easier to use and much more flexible).<br />
HTTP 1.0 clients will ignore &#8220;Cache-Control&#8221; but honour &#8220;Expires&#8221;. With &#8220;Expires&#8221; you get thus at least a bit control for these old clients.</p>
<p><a href="http://www.peterbe.com/plog/cache-control_or_expires">http://www.peterbe.com/plog/cache-control_or_expires</a></p></blockquote>
<p>In any event, Page Speed won&#8217;t protest if you do end up sending both Expires and Cache-Control max-age, or if you remove both Last-Modified and ETag, but I was able to get the best results with just setting Cache-Control max-age and removing the ETag.</p>
<h2>Setting the headers in .htaccess</h2>
<p>On Apache, configuring the proper headers can be done in the .htaccess file, using the <code>Header</code> directive.  The <code>Header</code> directive requires the <code>mod_headers</code> module to be enabled.</p>
<p>I&#8217;m choosing to set a far future Expires header of one year on my images files, because I tweak the CSS and JavaScript pretty often, and don&#8217;t want those file types to be cached as long.</p>
<p>Add the following code to your .htaccess file to set your Cache-Control and Expires headers, adjusting the date to be one year from today.</p>
<pre class="brush: plain; title: ; notranslate">
# Set Cache-Control and Expires headers
&lt;filesMatch &quot;\\.(ico|pdf|flv|jpg|jpeg|png|gif|swf|mp3|mp4)$&quot;&gt;
Header set Cache-Control &quot;max-age=2592000, private&quot;
Header set Expires &quot;Sun, 17 July 2011 20:00:00 GMT&quot;
&lt;/filesMatch&gt;
&lt;filesMatch &quot;\\.(css|css.gz)$&quot;&gt;
Header set Cache-Control &quot;max-age=604800, private&quot;
&lt;/filesMatch&gt;
&lt;filesMatch &quot;\\.(js|js.gz)$&quot;&gt;
Header set Cache-Control &quot;max-age=604800, private&quot;
&lt;/filesMatch&gt;
&lt;filesMatch &quot;\\.(xml|txt)$&quot;&gt;
Header set Cache-Control &quot;max-age=216000, private, must-revalidate&quot;
&lt;/filesMatch&gt;
&lt;filesMatch &quot;\\.(html|htm)$&quot;&gt;
Header set Cache-Control &quot;max-age=7200, private, must-revalidate&quot;
&lt;/filesMatch&gt;
</pre>
<h2>Removing ETags in .htaccess</h2>
<p>Most sources recommend simply removing ETags if they are not required.</p>
<blockquote><p>Entity tags (ETags) are a mechanism that web servers and browsers use to determine whether the component in the browser&#8217;s cache matches the one on the origin server.<br />
&#8230;<br />
If you&#8217;re not taking advantage of the flexible validation model that ETags provide, it&#8217;s better to just remove the ETag altogether.</p>
<p><a href="http://developer.yahoo.com/performance/rules.html#etags">http://developer.yahoo.com/performance/rules.html#etags</a>
</p></blockquote>
<p>Add the following code to your .htaccess file to remove ETag headers.</p>
<pre class="brush: plain; title: ; notranslate">
# Turn off ETags
FileETag None
Header unset ETag
</pre>
<h2>Set Expires headers with ExpiresByType (optional)</h2>
<p>If your host has the <code>mod_expires</code> module enabled, you can specify Expires headers by file type.  Godaddy does not have this module enabled.</p>
<pre class="brush: plain; title: ; notranslate">
# Set Expires headers
ExpiresActive On
ExpiresDefault &quot;access plus 1 year&quot;
ExpiresByType text/html &quot;access plus 1 second&quot;
ExpiresByType image/gif &quot;access plus 2592000 seconds&quot;
ExpiresByType image/jpeg &quot;access plus 2592000 seconds&quot;
ExpiresByType image/png &quot;access plus 2592000 seconds&quot;
ExpiresByType image/x-icon &quot;access plus 2592000 seconds&quot;
ExpiresByType text/css &quot;access plus 604800 seconds&quot;
ExpiresByType text/javascript &quot;access plus 604800 seconds&quot;
ExpiresByType application/x-javascript &quot;access plus 604800 seconds&quot;
</pre>
<h2>Removing the Last-Modified header in .htaccess (optional)</h2>
<p>I&#8217;m following Google&#8217;s instructions and not removing the Last-Modified header, but if you wanted to do so, you could use:</p>
<pre class="brush: plain; title: ; notranslate">
# Remove Last-Modified header
Header unset Last-Modified
</pre>
<h2>Busting the cache when files change</h2>
<p>What happens when you change files and need to force browsers to load the new files? Christian Johansen offers two methods in his post on <a href="http://cjohansen.no/en/apache/using_a_far_future_expires_header">Using a far future expires header</a>.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.ardamis.com/2010/07/17/sending-headers-to-leverage-browser-caching/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>

