<?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>Tommy Lacroix</title>
	<atom:link href="http://www.tommylacroix.com/feed/" rel="self" type="application/rss+xml" />
	<link>http://www.tommylacroix.com</link>
	<description>Personal Blog</description>
	<lastBuildDate>Tue, 08 Dec 2009 18:23:49 +0000</lastBuildDate>
	<generator>http://wordpress.org/?v=2.8.6</generator>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
			<item>
		<title>Cascaded Queueing</title>
		<link>http://www.tommylacroix.com/2009/12/08/cascaded-queueing/</link>
		<comments>http://www.tommylacroix.com/2009/12/08/cascaded-queueing/#comments</comments>
		<pubDate>Tue, 08 Dec 2009 17:18:13 +0000</pubDate>
		<dc:creator>tlacroix</dc:creator>
				<category><![CDATA[Uncategorized]]></category>

		<guid isPermaLink="false">http://www.tommylacroix.com/?p=135</guid>
		<description><![CDATA[I would guess every programmer knows what queueing is. For those who don’t, well, it’s some sort of waiting line for messages: they get in, they wait, and they get dispatched for processing.
I used queues many times in the past. First in first out (FIFO) queues for for mass email programs, last in first out [...]]]></description>
			<content:encoded><![CDATA[<p>I would guess every programmer knows what queueing is. For those who don’t, well, it’s some sort of waiting line for messages: they get in, they wait, and they get dispatched for processing.</p>
<p>I used queues many times in the past. First in first out (FIFO) queues for for mass email programs, last in first out (LIFO) queues for <a title="MicroBlogBuzz, Follow The Buzz" href="http://www.microblogbuzz.com" target="_blank">MicroBlogBuzz</a>, and prioritized queues for a video compression platform, among other things.</p>
<p>After a lot of trial and error, storage engine changes, code optimization, benchmarking and swearing, I came up with this idea of <em>cascaded queueing</em>. In a nutshell, cascaded queueing is putting two queues back to back: a master queue that will act as a warehouse, and one or many slave queues that will act as distributors or brokers.</p>
<p><span id="more-135"></span></p>
<p><strong>Available Queueing Methods</strong></p>
<p>The queue messages can be stored in different ways, and each have their pros and cons. The goal of this post not being to describe queueing, but rather cascaded queueing, here&#8217;s a quick analysis of the most common ones.</p>
<ul>
<li>File system: Quick, persistent and flexible, but it&#8217;s locally hosted, and it requires a file system that supports file locking.</li>
<li>Database (ie. <a title="MySQL, The world's most popular open source database" href="http://dev.mysql.com/" target="_blank">MySQL</a>): Persistent, networked and possibily distributed, but slow under high loads due to locking.</li>
<li>Memory (ie. <a title="Memcached, Free &amp; open source, high-performance, distributed memory object caching system" href="http://memcached.org/" target="_blank">Memcached</a>): Super fast for simple uses, can be distributed, but non persistent, and it&#8217;s simple nature renders prioritized queueing a bit complex.</li>
</ul>
<p>There also are full blown queueing solutions providing an API to enqueue and dequeue messages:</p>
<ul>
<li>Message Queueing Platforms (ie. <a title="Apache ActiveMQ, the most popular and powerful open source messaging and Integration Patterns provider." href="http://activemq.apache.org/" target="_blank">Apache ActiveMQ</a>): Full blown, but too heavy and/or complex for most simple uses.</li>
<li>External Message Queueing (ie. <a title="Amazon Simple Queue System (SQS)" href="http://aws.amazon.com/sqs/" target="_blank">Amazon Simple Queue System aka SQS</a>): Robust, simple and distributed, but on a pay per message and pay for bandwidth model, and there&#8217;s a REST payload for each request.</li>
</ul>
<p>The main idea behind cascaded queueing is to attenuate the cons of one queueing storage (poor performance or increased payload) by using two queueing methods simultaneously. With cascaded queueing, you would choose a master queue method for robustness, and the slave queue methods for performance.</p>
<p><strong>Theory Of Operation</strong></p>
<p>As with single queue queueing, all incoming messages get queued in the master queue.</p>
<p><img class="alignnone size-full wp-image-136" title="Master Queue" src="http://www.tommylacroix.com/wp-content/uploads/2009/12/cq-1.jpg" alt="Master Queue" width="270" height="35" /></p>
<p>However, the slave queue(s) communicate with the master queue, in order to make sure they keep enough messages locally to provide the processing nodes with them in a timely fashion. So they lock messages in the master queue, and host them locally. There can be a great performance gain here if the master queue is more efficient to locks and send messages in batch.</p>
<p><img class="alignnone size-full wp-image-137" title="Master Queue and Slave Queue" src="http://www.tommylacroix.com/wp-content/uploads/2009/12/cq-2.jpg" alt="Master Queue and Slave Queue" width="538" height="35" /></p>
<p>The processing nodes then lock and request messages to the slave queue. If the locks are only valid for a given amount of time, the slave queue must ensure that the processing node&#8217;s lock on the slave queue message must not exceed the slave queue lock on the master queue message. The slave queue may extend the lock on the master queue message if possible, or simply not return that message.</p>
<p><img class="alignnone size-full wp-image-138" title="Master Queue, Slave Queue and Processors" src="http://www.tommylacroix.com/wp-content/uploads/2009/12/cq-3.jpg" alt="Master Queue, Slave Queue and Processors" width="639" height="109" /></p>
<p>Finally, upon deletion of a message from the slave queue by the processing nodes, the deletion is cascaded to the master queue so the message is definitely removed.</p>
<p><img class="alignnone size-full wp-image-139" title="Deletion cascade" src="http://www.tommylacroix.com/wp-content/uploads/2009/12/cq-4.jpg" alt="Deletion cascade" width="639" height="109" /></p>
<p>A slave queue data loss will not cause any message to be lost, as they still exist in the master queue. The slave queue can therefore be shut down and rebuilt at anytime, which makes the use of Memcached as the slave queue backend storage possible.</p>
<p><strong>Test Driving It</strong></p>
<p>I first used this technique with MySQL as storage engine for the master queue, and Memcached as storage for the slave queue.</p>
<p>The big advantage of MySQL is that getting 200 messages isn’t a lot slower than getting 1: the locking takes pretty much the same time. However, the average processing time of each of my messages was quite high, and so was the standard deviation: processing a message took 15 seconds on average, but could take anywhere between 1 second and 60 seconds. Thus, processing nodes locking 200 messages at once often resulted in inefficient message distribution among them.</p>
<p>On the other hand, even if I find that Memcached is extremely reliable, it&#8217;s non persistent by definition. If you loose one of four nodes, you lose the quarter of your messages. And it’s even worst if your memory gets full: the oldest messages are deleted. But it’s extremely fast, as there’s no disk IO.</p>
<p>Cascading MySQL to Memcached improved the queue performance a lot by removing almost all MySQL inter-locking dead times . Messages were be transferred in batches of 200 to the slave queue every time the queue was at 100 messages or lower, and they were be dequeued one by one by the processing nodes.</p>
<p>As I only had one processing environment, I hosted my slave queue and my master queue at the same place. But one could imagine a slave queue per processing environment as well.</p>
<p><img style="border: 0px initial initial;" title="Single Master Queue, Multiple Slave Queues" src="http://www.tommylacroix.com/wp-content/uploads/2009/12/cq-5.jpg" alt="Single Master Queue, Multiple Slave Queues" width="640" height="256" /></p>
<p><strong>Final note</strong></p>
<p>Obviously, requeueing everything a second time, extending locks and cascading delete requests means extra payload and processing time. Moreover, if not implemented properly, you run the risk of seeing your slave queue repeatedly go empty, having your processing nodes waiting for it to be refilled.</p>
<p>Therefore, cascaded queueing is not the answer to all queueing problem, but rather a way to work around some problems encountered with some queueing methods.</p>
<p><strong><br />
</strong></p>
]]></content:encoded>
			<wfw:commentRss>http://www.tommylacroix.com/2009/12/08/cascaded-queueing/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>PHP based MP4/F4V meta data reader</title>
		<link>http://www.tommylacroix.com/2009/06/11/mp4-and-f4v-php-flash-video-meta-data-reader/</link>
		<comments>http://www.tommylacroix.com/2009/06/11/mp4-and-f4v-php-flash-video-meta-data-reader/#comments</comments>
		<pubDate>Thu, 11 Jun 2009 14:45:40 +0000</pubDate>
		<dc:creator>tlacroix</dc:creator>
				<category><![CDATA[Flash]]></category>
		<category><![CDATA[PHP]]></category>
<category>duration</category><category>f4v</category><category>flash</category><category>flv</category><category>height</category><category>meta</category><category>meta data</category><category>mp4</category><category>mp4info</category><category>php</category><category>tool</category><category>width</category>
		<guid isPermaLink="false">http://www.tommylacroix.com/?p=124</guid>
		<description><![CDATA[
You might have read my post about my FLV meta data and cue point reader/writer. You might also know that the F4V format (which really is simple MP4+H264+AAC) succeeded to the FLV format, and is supported by Flash 9.0.115 and up.
The MP4Info class is a simple extensible PHP class reading the MP4 container&#8217;s frames (called [...]]]></description>
			<content:encoded><![CDATA[<p><img style="float:left;margin:0 10px 10px 0;" title="F4V File Format" src="http://www.tommylacroix.com/wp-content/uploads/2009/06/f4v.gif" alt="F4V File Format" width="128" height="128" /><br />
You might have read my post about my <a title="PHP based FLV meta and cue points reader/writer" href="http://www.tommylacroix.com/2008/07/04/a-php-tool-to-modify-an-flv-meta-and-cuepoints-on-the-fly/" target="_self">FLV meta data and cue point reader/writer</a>. You might also know that the <a title="Flash Video, at Wikipedia" href="http://en.wikipedia.org/wiki/Flash_Video#File_formats" target="_blank">F4V format</a> (which really is simple MP4+H264+AAC) succeeded to the FLV format, and is supported by Flash 9.0.115 and up.</p>
<p>The <a title="php-mp4info, at Google Code" href="http://code.google.com/p/php-mp4info/" target="_blank">MP4Info class</a> is a simple extensible PHP class reading the MP4 container&#8217;s frames (called boxes) to get various information, namely the video duration, the video/audio codecs, the width and the height, as well as the embedded <a title="Adobe's Extensible Meta-data Platform (XMP), at Wikipedia" href="http://en.wikipedia.org/wiki/Extensible_Metadata_Platform" target="_blank">XMP meta data</a>.</p>
<p>The F4V format is better than the FLV format in many aspects:</p>
<ul>
<li>Better compression ratio</li>
<li>Better quality at similar bit rates</li>
<li>Open source compressors available, through ffmpeg and libh264 (Flash 8 On2 VP6 codec is proprietary)</li>
<li>Decoding H.264 is a lot less CPU intensive on the host computer than decoding On2 VP6</li>
</ul>
<div>With <a title="Adobe Flash Player Version Penetration" href="http://www.adobe.com/products/player_census/flashplayer/version_penetration.html" target="_blank">Flash 9 being at a rate of penetration of 98.9% in mature markets</a><sup>1</sup> and will all the advantages, MP4 Flash video is becoming more and more common.</div>
<div><span id="more-124"></span></div>
<div>It really only has been tested with Sorenson Squeeze encoded F4V files for now, and therefore should be considered beta.</p>
<p>You&#8217;ll find the project <a title="php-mp4info, at Google Code" href="http://code.google.com/p/php-mp4info/" target="_blank">at Google Code, under php-mp4info</a>.</p>
<p><small><sup>1</sup> As per Adobe&#8217;s Adobe Flash Player Version Penetration statistics, on June 11, 2009<br />
<sup>2</sup> F4V logo at the top found at <a title="Flash Develop And Design" href="http://www.flashdevelop.net/postshow_975.html" target="_blank">Flash Develop And Design</a></small></div>
]]></content:encoded>
			<wfw:commentRss>http://www.tommylacroix.com/2009/06/11/mp4-and-f4v-php-flash-video-meta-data-reader/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>Watermarker: A tool for pixel-perfect web integration</title>
		<link>http://www.tommylacroix.com/2009/01/13/watermarker-a-tool-for-pixel-perfect-web-integration/</link>
		<comments>http://www.tommylacroix.com/2009/01/13/watermarker-a-tool-for-pixel-perfect-web-integration/#comments</comments>
		<pubDate>Tue, 13 Jan 2009 21:45:00 +0000</pubDate>
		<dc:creator>tlacroix</dc:creator>
				<category><![CDATA[CSS]]></category>
		<category><![CDATA[HTML]]></category>
		<category><![CDATA[integration]]></category>
		<category><![CDATA[Programming]]></category>
		<category><![CDATA[tools]]></category>
<category>css</category><category>html</category><category>integration</category><category>programming</category><category>tools</category>
		<guid isPermaLink="false">http://www.tommylacroix.com/?p=117</guid>
		<description><![CDATA[Having worked with many fastidious web designers in the past requesting that every single integrated page looks precisely to the pixel like the PSD file &#8212; you know who you are &#8212; I developped this little JavaScript technique that allows the integrator to enable/disable a 80% alpha JPEG image overlay, in order to visually find the discrepancies. [...]]]></description>
			<content:encoded><![CDATA[<p>Having worked with many fastidious web designers in the past requesting that every single integrated page looks precisely to the pixel like the PSD file &#8212; you know who you are &#8212; I developped this little JavaScript technique that allows the integrator to enable/disable a 80% alpha JPEG image overlay, in order to visually find the discrepancies. The code is very ugly but cross-browser compatible, and I never felt compelled to make it better as it does perfectly what it has to do.</p>
<p>It&#8217;s quite easy to use: simply load the JavaScript file at the end of your HTML file, and have it point on your JPEG image to an default X/Y position.</p>
<pre class="code">&lt;script language="javascript" type="text/javascript" src="watermarker.js"&gt;&lt;/script&gt;
&lt;script language="javascript" type="text/javascript"&gt;
setWatermark("images/mywatermark.jpg",-4,-5);
&lt;/script&gt;</pre>
<p>And voila, you can toggle your JPEG overlay, move it around, change it&#8217;s alpha transparency and reset it to its default position.</p>
<p><a title="Watermarker Demo" href="http://www.tommylacroix.com/demos/watermarker/" target="_blank">A small demo is available here</a>.</p>
<p><a title="Watermarker JavaScript source" href="http://www.tommylacroix.com/demos/watermarker/watermarker.js.txt"><img style="border: 0pt none ;" src="http://www.tommylacroix.com/img/icons/script_code.gif" alt="PHP Source" width="16" height="16" /></a> <a title="Watermarker JavaScript source" href="http://www.tommylacroix.com/demos/watermarker/watermarker.js.txt">watermarker.js</a> <em>(7.8k)</em></p>
]]></content:encoded>
			<wfw:commentRss>http://www.tommylacroix.com/2009/01/13/watermarker-a-tool-for-pixel-perfect-web-integration/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>I present you with MicroBlogBuzz</title>
		<link>http://www.tommylacroix.com/2008/09/19/i-present-you-with-microblogbuzz/</link>
		<comments>http://www.tommylacroix.com/2008/09/19/i-present-you-with-microblogbuzz/#comments</comments>
		<pubDate>Fri, 19 Sep 2008 21:35:32 +0000</pubDate>
		<dc:creator>tlacroix</dc:creator>
				<category><![CDATA[Personal]]></category>
		<category><![CDATA[Social networks]]></category>
		<category><![CDATA[Web development]]></category>
		<category><![CDATA[micro-blogging]]></category>
		<category><![CDATA[optimization]]></category>
		<category><![CDATA[PHP]]></category>
<category>micro-blogging</category><category>optimization</category><category>personal</category><category>php</category><category>web development</category>
		<guid isPermaLink="false">http://www.tommylacroix.com/?p=114</guid>
		<description><![CDATA[At the beginning of the week, I officially launched a little project named MicroBlogBuzz. The concept is simple: find URLs on micro-blogging platforms, and present the top blogged ones.
For those not familiar with micro-blogging, it&#8217;s pretty much like blogging, but smaller &#8212; hence the micro prefix. Micro-blogging posts are very short, 140-160 characters that is. [...]]]></description>
			<content:encoded><![CDATA[<p>At the beginning of the week, I officially launched a little project named <a title="MicroBlogBuzz, micro-blogging linking statistics" href="http://microblogbuzz.com" target="_blank">MicroBlogBuzz</a>. The concept is simple: find URLs on micro-blogging platforms, and present the top blogged ones.</p>
<p>For those not familiar with micro-blogging, it&#8217;s pretty much like blogging, but smaller &#8212; hence the micro prefix. Micro-blogging posts are very short, 140-160 characters that is. The most popular platform is <a title="Twitter" href="http://www.twitter.com" target="_blank">Twitter</a>, but new platforms are appearing, such as <a title="Pownce" href="http://pownce.com" target="_blank">Pownce</a>, <a title="Jaiku" href="http://www.jaiku.com" target="_blank">Jaiku</a> and <a title="Identica" href="http://identi.ca" target="_blank">Identica</a>. Also, this year&#8217;s <a title="TechCrunch 50" href="http://www.techcrunch50.com/">TechCrunch50</a> winner is the <a title="Yammer, the project oriented micro-blogging platform" href="http://www.yammer.com/" target="_blank">project oriented micro-blogging platform Yammer</a> (which I didn&#8217;t get to try yet).</p>
<p>Back to MicroBlogBuzz, I started this little project part time while testing various APIs, and when stumbled on <a title="TwitterBuzz" href="http://www.twitterbuzz.com">TwitterBuzz</a>, which presents the most popular links on Twitter. However, TwitterBuzz only present the domain name, which isn&#8217;t really meaningful since most micro-bloggers use <a title="URL shortener TinyURL" href="http://tinyurl.com" target="_blank">TinyURL</a> or similar services to shorten their URL. TinyURL was presented as the Top Twitter link. And I thought it was kind of stupid. So I decided to do the same thing, but to follow the HTTP redirections to get to the final URL.</p>
<p><span id="more-114"></span></p>
<p>I got surprised by the <a title="MicroBlogBuzz statistics" href="http://microblogbuzz.com/stats/" target="_blank">quantity of data that I would collect</a>. Around 1200 links every 15 minutes, and over 330,000 links and 400,000 comments in five days. I quickly ran into problems, as I built my database on InnoDB and foreign keys to keep things clean, and with this amount of data and the small server it runs on, well it can&#8217;t be clean and fast at the same time.</p>
<p>So I switched to MyISAM. But still, it wasn&#8217;t enough. And I added caching and smart HTTP headers. But still, it wasn&#8217;t enough. And I added preprocessing. And it was ok. At least for now, with my small 500 visitors per day.</p>
<p>Feel free to send me your comments and suggestions, by email, or even better, on Twitter <img src='http://www.tommylacroix.com/wp-includes/images/smilies/icon_wink.gif' alt=';-)' class='wp-smiley' /> </p>
]]></content:encoded>
			<wfw:commentRss>http://www.tommylacroix.com/2008/09/19/i-present-you-with-microblogbuzz/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>PHP Design Pattern: Building a Tree</title>
		<link>http://www.tommylacroix.com/2008/09/10/php-design-pattern-building-a-tree/</link>
		<comments>http://www.tommylacroix.com/2008/09/10/php-design-pattern-building-a-tree/#comments</comments>
		<pubDate>Wed, 10 Sep 2008 17:12:27 +0000</pubDate>
		<dc:creator>tlacroix</dc:creator>
				<category><![CDATA[PHP]]></category>
		<category><![CDATA[Programming]]></category>
		<category><![CDATA[pattern]]></category>
		<category><![CDATA[tree]]></category>
		<category><![CDATA[tree building]]></category>
<category>pattern</category><category>php</category><category>tree</category><category>tree building</category>
		<guid isPermaLink="false">http://www.tommylacroix.com/?p=89</guid>
		<description><![CDATA[Categories and sub categories. Directories and sub directories. Building a tree of nodes that have parents is a common problem these days.
There are two solutions to this problem, but unfortunately, the inefficient one is often used by programmers, due to the lack of time, or to inexperience.

Imagine, for the sake of clarity, a simple dataset that [...]]]></description>
			<content:encoded><![CDATA[<p><img style="float:right; border:1px solid black; margin-left: 5px; margin-bottom: 5px; padding:5px; background-color:white;" src="/wp-content/uploads/2008/09/imagebinarytree.jpg" alt="Tree, in computing" width="289" height="253" />Categories and sub categories. Directories and sub directories. Building a <a title="Tree, at Wikipedia" href="http://en.wikipedia.org/wiki/Tree_(data_structure)" target="_blank">tree </a>of nodes that have parents is a common problem these days.</p>
<p>There are two solutions to this problem, but unfortunately, the inefficient one is often used by programmers, due to the lack of time, or to inexperience.</p>
<p><span id="more-89"></span></p>
<p>Imagine, for the sake of clarity, a simple dataset that goes as follow:</p>
<pre class="code">Array
(
    [0] =&gt; Array
        (
            [name] =&gt; Node 0
            [parent] =&gt; null
        )

    [1] =&gt; Array
        (
            [name] =&gt; Node 1
            [parent] =&gt; 4
        )

    [2] =&gt; Array
        (
            [name] =&gt; Node 2
            [parent] =&gt; 8
        )
    ...</pre>
<p>The common approach is to use a recursive function that finds all the root nodes, then all their sub nodes, then all the sub nodes of their sub nodes, and so on.</p>
<pre class="code">function mapTree($dataset, $parent=null) {
	$tree = array();
	foreach ($dataset as $id=&gt;$node) {
		if ($node['parent'] !== $parent) continue;
		$node['children'] = mapTree($dataset, $id);
		$tree[$id] = $node;
	}

	return $tree;
}</pre>
<p>The problem with this method is that, in terms of calculation, the problem complexity is exponential: you need to search all the dataset for children nodes for each node. Thus, building a tree of 1,000 nodes is 100 times more complicated than building a tree of 100 nodes, because you will have to search 10 times more nodes, 10 times more often.</p>
<p>A more efficient way to build the tree is to use dereferencing. This allows to map the whole tree in a single pass, without recursion. The problem complexity then becomes linear rather than exponential, and 10 times more nodes equals to 10 times more time (although the time required by PHP to find a specific index of an array isn&#8217;t taken into account here).</p>
<pre class="code">function mapTree($dataset) {
	$tree = array();
	foreach ($dataset as $id=&gt;&amp;$node) {
		if ($node['parent'] === null) { // root node
			$tree[$id] = &amp;$node;
		} else { // sub node
			if (!isset($dataset[$node['parent']]['children'])) $dataset[$node['parent']]['childs'] = array();
			$dataset[$node['parent']]['children'][$id] = &amp;$node;
		}
	}

	return $tree;
}</pre>
<p>The full PHP code of examples can be found below:</p>
<p><a title="Tree building pattern example" href="http://www.tommylacroix.com/wp-content/uploads/2008/09/tree.zip"><img style="border: 0pt none ;" src="http://www.tommylacroix.com/img/icons/script_code.gif" alt="PHP Source" width="16" height="16" /></a> <a title="Tree building pattern example" href="http://www.tommylacroix.com/wp-content/uploads/2008/09/tree.zip">tree.zip</a> <em>(1.2k)</em></p>
<p><small>Tree image from <a title="Binary Tree Image" href="http://en.wikipedia.org/wiki/Image:Binary_tree.svg">Wikipedia.org</a></small></p>
]]></content:encoded>
			<wfw:commentRss>http://www.tommylacroix.com/2008/09/10/php-design-pattern-building-a-tree/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>HTTP caching basics</title>
		<link>http://www.tommylacroix.com/2008/07/17/http-caching-basics/</link>
		<comments>http://www.tommylacroix.com/2008/07/17/http-caching-basics/#comments</comments>
		<pubDate>Thu, 17 Jul 2008 18:56:56 +0000</pubDate>
		<dc:creator>tlacroix</dc:creator>
				<category><![CDATA[PHP]]></category>
		<category><![CDATA[Usability]]></category>
		<category><![CDATA[browser]]></category>
		<category><![CDATA[caching]]></category>
		<category><![CDATA[http]]></category>
		<category><![CDATA[http caching]]></category>
		<category><![CDATA[http headers]]></category>
		<category><![CDATA[performance]]></category>
		<category><![CDATA[proxy]]></category>
<category>browser</category><category>caching</category><category>http</category><category>http headers</category><category>performance</category><category>php</category><category>proxy</category>
		<guid isPermaLink="false">http://www.tommylacroix.com/?p=70</guid>
		<description><![CDATA[The HTTP protocol is quite simple. But many of us under-use it, programmatically speaking. There are many very simple performance mechanisms that are often forgotten. Many developers go for disabling HTTP caching completely, as they often don&#8217;t understand how to use it, and because it can cause weird bugs when used incorrectly.
But so much things [...]]]></description>
			<content:encoded><![CDATA[<p>The <a title="Hyper-Text Transfer Protocol, at Wikipedia" href="http://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol" target="_blank">HTTP protocol</a> is quite simple. But many of us under-use it, programmatically speaking. There are many very simple performance mechanisms that are often forgotten. Many developers go for disabling HTTP caching completely, as they often don&#8217;t understand how to use it, and because it can cause weird bugs when used incorrectly.</p>
<p>But so much things are cacheable: pages, images, CSS, JavaScript, even many REST web services! Yes, even in this social web era where content changes faster than you can write, there&#8217;s still plenty of slow changing information, such as home pages, or lists of countries, regions and cities.</p>
<p>Efficiently using caching translates into:</p>
<ul>
<li>Better response and loading time</li>
<li>Decreased load on the server</li>
<li>Better user experience</li>
</ul>
<p>This article aims to present a simple explanation of the HTTP protocol and proper use of HTTP caching.</p>
<p><span id="more-70"></span></p>
<h3>The HTTP Protocol basics</h3>
<p>The HTTP protocol is a communication scheme between two or three actors: the server, the browser, and the often forgotten proxy.</p>
<p>First, to see the implicated headers, let&#8217;s have a look to the typical request &#8230;</p>
<pre class="code">GET / HTTP/1.1
Host: www.tommylacroix.com
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
<strong>If-Modified-Since: Thu, 17 Jul 2008 16:11:24 GMT
If-None-Match: "a9432-fc1b28423-cb122da"</strong>
<strong>User-Agent: Mozilla/5.0 (...) Gecko/2008052906 Firefox/3.0</strong></pre>
<p>&#8230; and response &#8230;</p>
<pre class="code">HTTP/1.1 200 OK
Date: Thu, 17 Jul 2008 16:11:24 GMT
<strong>Expires: Wed, 11 Jan 1984 05:00:00 GMT
Etag: "a9432-fc1b28423-cb122da"
Last-Modified: Thu, 17 Jul 2008 16:11:24 GMT
Cache-Control: private, must-revalidate, max-age=0
Vary: User-Agent
</strong>Content-Type: text/html; charset=UTF-8

&lt;html&gt;...&lt;/html&gt;</pre>
<h3>Basic Caching</h3>
<p>The response headers returned by the server give the browser and the proxy information to make caching decisions.</p>
<p>First, the <a title="The Cache-Control header, in HTTP specifications, at W3.org" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9" target="_blank"><code>Cache-Control</code> header</a> tells if the content is cacheable or not. If they&#8217;re not sent, it&#8217;s cacheable by default. Consider the following headers:</p>
<pre class="code">Cache-Control: public, max-age=86400</pre>
<p>It basically says that the page is cacheable in a <code>public</code> scope, and that the content shouldn&#8217;t be kept in the cache without revalidation for more than 1 day (86400 seconds). The scope can be:</p>
<ul>
<li><code>public</code>: Cacheable by browsers and proxies, even if authenticated</li>
<li><code>Private</code>: Cacheable by browsers only (and proxies, but only for requests from the same clients)</li>
<li><code>no-cache</code>: Cacheable, but revalidation is required every time.</li>
<li><code>no-store</code>: Not cacheable at all.</li>
</ul>
<p>In addition, there are a few other keywords that you can add to your <code>Cache-Control</code> header. Keep in mind that you must separate multiple keywords by commas.</p>
<ul>
<li><code>must-revalidate</code>: Some proxies can be configured to ignore the <code>Expires</code> and <code>maxage</code>. This keyword forces them to always act like the resource was expired.</li>
<li><code>proxy-revalidate</code>: Same as <code>must-revalidate</code>, but only for proxies.</li>
<li><code>s-maxage</code>: Same as <code>maxage</code>, but only for proxies.</li>
</ul>
<p>Finally, for HTTP/1.0 compatibility, you should send a <code>Pragma</code> header when using the <code>Cache-Control no-cache</code> directive.</p>
<pre class="code">Pragma: no-cache</pre>
<p><strong>Browsers and proxies cache resources based on their URL</strong>. You can take advantage of this in many ways at the design stage of your web site or web application. For example, you should put non private REST web service parameters (such as language, or country) in the URL:</p>
<pre class="code">http://www.somesite.com/webservice/regionslist/country/usa/language/en</pre>
<p>Browsers and proxies also store three important information about the page: the <code>Expires</code>, <code>Last-Modified</code> and <code>Etag</code> headers.</p>
<ul>
<li>The <a title="The Expires header, in HTTP specifications, at W3.org" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.21" target="_blank"><code>Expires</code> header</a> tells the browser and proxy, along with the <code>max-age</code> component of the <code>Cache-Control</code> header, until when this version of the content should be valid.</li>
<li>The <a title="The Last-Modified header, in HTTP specifications, at W3.org" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.29" target="_blank"><code>Last-Modified</code> header</a> tells the browser and proxy the date and time of the last modification to this page. It isn&#8217;t always provided.</li>
<li>The <a title="The Etag header, in HTTP specifications, at W3.org" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.19" target="_blank"><code>Etag</code> header</a>, which stands for Entity Tag, gives the browser and proxy a unique identifier that describes the content it returned. If a page&#8217;s content changes, its <code>Etag</code> changes as well.</li>
</ul>
<p>When you requested this page, your browser (and the proxy in the middle if there was one) did check if the page is cacheable, and since it was, stored a copy of the page associated with the URL, and the three metrics above. For subsequent requests, the server might use this cached copy.</p>
<p>Might? Yes, because the cached page won&#8217;t be good until the end of times. At some point, the browser (or the proxy) will check with the server if the page it has in cache is still valid. This is called revalidation, and the <code>Expires</code> HTTP response header along with the <code>max-age</code> component of the <code>Cache-Control</code> header control it.<a href="http://www.tommylacroix.com/wp-content/uploads/2008/07/http-cache-browser.gif"><img class="aligncenter size-full wp-image-71" title="HTTP caching, with the browser\'s perspective" src="http://www.tommylacroix.com/wp-content/uploads/2008/07/http-cache-browser.gif" alt="" width="476" height="267" /></a></p>
<p>When the browser or proxy revalidates a page, it sends information about its version: the <code>Etag</code>, and the <code>Last-Modified</code> the server sent when he cached the page. These are sent as <a title="The If-Modified-Since header, in HTTP specifications, at W3.org" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.25" target="_blank"><code>If-Modified-Since</code></a>, and <a title="The If-None-Match header, in HTTP specifications, at W3.org" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.26" target="_blank"><code>If-None-Match</code></a>, respectively:</p>
<pre class="code">GET /some-cachable-page HTTP/1.1
Host: www.tommylacroix.com
<strong>If-Modified-Since: Thu, 17 Jul 2008 16:11:24 GMT
If-None-Match: "a9432-fc1b28423-cb122da"
</strong>User-Agent: Mozilla/5.0 (...) Gecko/2008052906 Firefox/3.0</pre>
<p>The server then compares this information with the <code>Etag</code> and the <code>Last-Modified</code> of the up-to-date page. If the browser&#8217;s cached copy appears to be valid, the server replies with the <code>Expires</code> and <code>Etag</code> (if available), headers, and no content:</p>
<pre class="code">HTTP/1.1 304 Not modified
Date: Thu, 17 Jul 2008 16:11:24 GMT
Expires: Wed, 11 Jan 2009 05:00:00 GMT
Etag: "a9432-fc1b28423-cb122da"</pre>
<p>If the browser&#8217;s cached copy appears to be out-dated, the server replies with the whole page, as usual.</p>
<p><a href="http://www.tommylacroix.com/wp-content/uploads/2008/07/http-cache-server.gif"><img class="aligncenter size-full wp-image-72" title="HTTP caching, with the server\'s perspective" src="http://www.tommylacroix.com/wp-content/uploads/2008/07/http-cache-server.gif" alt="" width="417" height="308" /></a></p>
<h3>Specific Cases</h3>
<p>Now, here comes the tricky cases. For the sake of clarity, I&#8217;ll use the following plot for the scenarios below:</p>
<blockquote><p>« Bob, Alice and Gregg work in the same office. Their office is equiped with a caching web proxy. Bob and Gregg share the same computer with the same user (ok, not credible, so lets say it&#8217;s a under-financed non-profit organization), and Alice has her own (she&#8217;s the boss).  »</p></blockquote>
<h4>Scenario 1: Secure sessions</h4>
<blockquote><p>« Bob goes on his SuperSocial profile page, at http://www.supersocial.com/profile/. His browser and the office proxy will check if the page is cacheable, and it is. They both store a copy of the page associated with the URL.</p>
<p>But what happens when Gregg or Alice log in his/her SuperSocial&#8217;s profile page right after? The browser will give him/her Bob&#8217;s page! »</p></blockquote>
<p><strong>The <code>Cache-Control</code>, <code>Expires</code>, and the <code>Etag</code> headers. </strong>Setting the <code>Expires</code> header in the past and the <code>max-age</code> to zero will cause the browser to revalidate the content each time. If the cached content is valid, only a header with no content will be sent. This is slightly slower than full blown caching, but no as much as no caching at all.</p>
<p>We could also set the <code>Cache-Control</code> scope to private, as each copy of the page will obviously only be valid one user.</p>
<p>We should also use <code>Etag</code>, as the content returned for the same URL isn&#8217;t the same for Bob and for Gregg, so the <code>Etag</code> will change, and the browser will reload the page for Gregg.</p>
<h4>Scenario 2: Content optimization</h4>
<blockquote><p>« The three unproductive workers browse a news site that has a wicked design. So wicked that the guys behind it had to make browser specific optimizations. Therefore, when you&#8217;re with Internet Explorer, the page is IE optimized, when you&#8217;re with Safari, some in-line CSS styles are different, and when you&#8217;re with Firefox, you get the regular page because this beauty is standard-compliant. Standard-what? Lets not digress&#8230;</p>
<p>When Bob and Gregg browse the site with IE from the same computer, it&#8217;s fine. But when Alice accesses it a short while after with Safari, she gets an ugly page as it&#8217;s IE optimized. The reason is, the server sent the IE version to Bob, and the proxy cached it. When Alice requests it, the proxy sends the IE page, thinking that it&#8217;s all fine. »</p></blockquote>
<p><strong>The <code>Vary</code> header.</strong> When the content of a page changes based on miscellaneous headers contained in the request, the server must tell the browser and the proxy using the <a title="The Vary header, in HTTP specifications, at W3.org" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.44" target="_blank"><code>Vary</code> header</a>. The <code>Vary</code> header basically says: If you cache this content, beware that if this header changes, the content might change as well, so use the URL with these fields to make sure you serve a valid version later.</p>
<p>Here&#8217;s an example. The request&#8230;</p>
<pre class="code">GET / HTTP/1.1
Host: www.tommylacroix.com
User-Agent: Mozilla/5.0 (...) Gecko/2008052906 Firefox/3.0</pre>
<p>&#8230; and response &#8230;</p>
<pre class="code">HTTP/1.1 200 OK
Date: Thu, 17 Jul 2008 16:11:24 GMT
Expires: Wed, 11 Jan 2009 05:00:00 GMT
Last-Modified: Thu, 17 Jul 2008 16:11:24 GMT
Cache-Control: public, max-age=86400
Vary: User-Agent
Content-Type: text/html; charset=UTF-8</pre>
<h3>Code Snippet</h3>
<p>Here&#8217;s a code snippet I wrote quite some time ago. It&#8217;s not pretty, but it works so I think it&#8217;s a good basic practical example.</p>
<pre class="code">/**
* setCacheHTTPHeaders
*
* @author	Tommy Lacroix
* @param	string	$privacy		Scope: public, private or no-cache
* @param	int	$lastModified		Unix timestamp of last page modification (optional)
* @param	int	$maxage			Maximum caching time before revalidation (optional)
* @param	string	$etag			Entity tag, page-content specific (optional)
* @return	bool				TRUE if content need to be sent, FALSE if no content need to be sent
*/
function setCachePolicy($privacy = 'public', $lastModified = false, $maxage = false, $etag = false) {
	// Sanitize privacy
	switch ($privacy) {
		case 'privacy':
		case 'public':
		case 'no-cache':
			break;
		default:
			$privacy = "public";
			break;
	}

	// Calculate expiry and max-age
	if (is_string($maxage)) { // Expiry is a string, interpret
		unset($m);
		if (preg_match('/^([0-9]+)([smhdwy])$/', $maxage, $m)) {
			$maxage = $m[1];
			switch ($m[2]) {
				case 's':	break;
				case 'm':	$maxage *= 60; 		break;
				case 'h':	$maxage *= 3600; 	break;
				case 'd':	$maxage *= 86400; 	break;
				case 'w':	$maxage *= 604800; 	break;
				case 'y':	$maxage *= 31536000; 	break;
			}
		}
	}
	if ($privacy != 'no-cache') {
		header('Expires: '.gmdate("r", time()+$maxage));
	} else {
		header('Expires: '.gmdate("r", time()-31536000));
		$maxage = 0;
	}

	// Send ETag headers
	if ($etag !== false) {
		header('ETag: "'.$etag.'"');
	}

	// Determine wheter we need to send content or not
	$outputContent = true;

	// Check ETag
	if ((isset($_SERVER['HTTP_IF_NONE_MATCH'])) &amp;&amp; ($etag !== false)) {
		if ($_SERVER['HTTP_IF_NONE_MATCH'] == '"'.$etag.'"') {
			header($_SERVER['SERVER_PROTOCOL'].' 304 Not Modified');
			$outputContent = false;
		}
	}

	if ((isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) &amp;&amp; ($lastModified !== false)) {
		$ifModifiedSince = strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']);
		if ($ifModifiedSince &gt;= $lastModified) {
			header($_SERVER['SERVER_PROTOCOL'].' 304 Not Modified');
			$outputContent = false;
		}
	}

	// Remove content if output has been disabled
	if (!$outputContent) {
		// Return false: You don't need to send content
		return false;
	} else {
		// Send other headers
		header('Cache-Control: '.$privacy.', must-revalidate, post-check=0, pre-check=0, max-age='.$maxage);
		if ($privacy == 'no-cache') header('Pragma: no-cache');
		if ($lastModified !== false) header('Last-Modified: '.gmdate('r',$lastModified));

		// Return true: you need to send content
		return true;
	}
}</pre>
<h3>Conclusion</h3>
<p>The caching HTTP headers are simple to implement, and provide a huge performance bonus. If done properly, this convert into a better user experience, as there&#8217;s less waiting, and more browsing.</p>
<p>Shall you like me to add other scenarios, feel free to drop me a line and I&#8217;ll see what I can do.</p>
<h3>Further Reading</h3>
<ul>
<li><a title="Caching in HTTP, at W3" href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html" target="_blank">Caching in HTTP, at W3</a></li>
<li><a title="HTTP Caching in Mozilla, at Mozilla" href="http://www.mozilla.org/projects/netlib/http/http-caching-faq.html" target="_blank">HTTP Caching in Mozilla, at Mozilla</a></li>
</ul>
]]></content:encoded>
			<wfw:commentRss>http://www.tommylacroix.com/2008/07/17/http-caching-basics/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Super-charged Amazon SQS Sample in PHP</title>
		<link>http://www.tommylacroix.com/2008/07/11/super-charged-amazon-sqs-sample-in-php/</link>
		<comments>http://www.tommylacroix.com/2008/07/11/super-charged-amazon-sqs-sample-in-php/#comments</comments>
		<pubDate>Fri, 11 Jul 2008 20:00:58 +0000</pubDate>
		<dc:creator>tlacroix</dc:creator>
				<category><![CDATA[PHP]]></category>
		<category><![CDATA[amazon]]></category>
		<category><![CDATA[example]]></category>
		<category><![CDATA[sample]]></category>
		<category><![CDATA[simple queue service]]></category>
		<category><![CDATA[sqs]]></category>
<category>amazon</category><category>example</category><category>php</category><category>sample</category><category>simple queue service</category><category>sqs</category>
		<guid isPermaLink="false">http://www.tommylacroix.com/?p=69</guid>
		<description><![CDATA[If you played with the pretty cool Amazon Web Services, you probably started off fetching a sample off the Developer&#8217;s Connection pages. At least, I did.
One cool sample for Simple Queue Service is the one by Justin@AWS. But (you saw it coming, I suppose), being anti-PEAR (for many reason, such as dependencies, weight, version conflicts, [...]]]></description>
			<content:encoded><![CDATA[<p>If you played with the pretty cool <a title="Amazon's Web Services" href="http://www.amazon.com/gp/browse.html?node=3435361" target="_blank">Amazon Web Services</a>, you probably started off fetching a sample off the <a title="Amazon Web Services Developer's Connection" href="http://developer.amazonwebservices.com/connect/index.jspa">Developer&#8217;s Connection pages</a>. At least, I did.</p>
<p>One cool sample for <a title="Simple Queue Service, at Amazon's Web Services" href="http://www.amazon.com/gp/browse.html?node=3435361" target="_blank">Simple Queue Service</a> is the one by <a title="Amazon SQS Sample in PHP by Justin@AWS" href="http://developer.amazonwebservices.com/connect/entry.jspa?externalID=1180" target="_blank">Justin@AWS</a>. But (you saw it coming, I suppose), being anti-<a title="PEAR - PHP Extension and Application Repository, at PHP.net" href="http://pear.php.net/" target="_blank">PEAR</a> (for many reason, such as dependencies, weight, version conflicts, non-catchiness of the name, fructose intolerance, etc.), I just couldn&#8217;t leave the sample like that. Yes, this is me again reinventing the wheel.</p>
<p>The original library depended on two PEAR components:</p>
<ul>
<li><a title="Crypt_HMAC PEAR package, at PHP.net" href="http://pear.php.net/package/Crypt_HMAC" target="_self"><code>Crypt_HMAC</code></a>, for authentication</li>
<li><a title="HTTP_Request PEAR package, at PHP.net" href="http://pear.php.net/package/HTTP_Request" target="_blank"><code>HTTP_Request</code></a>, for HTTP calls</li>
</ul>
<h3><span id="more-69"></span>The Crypt_HMAC replacement</h3>
<p>I replaced the <code>Crypt_HMAC</code> dependency by three potential routines: <a title="Hash PECL, at PHP.net" href="http://ca.php.net/manual/en/book.hash.php" target="_blank"><code>hash PECL</code></a>, <a title="mhash, at PHP.net" href="http://ca.php.net/manual/en/book.hash.php" target="_blank"><code>mhash</code></a>, and <a title="Lance's HMAC function, at PHP.net" href="http://ca.php.net/manual/en/function.mhash.php">Lance&#8217;s function</a> (which you will never hear in a Numb3r&#8217;s episode, as it has nothing to do with a mathematician, but rather with a smart dude who posted a fully PHP HMAC code snippet in the PHP comments of the <code>mhash()</code> function).</p>
<p>The code goes as follow:</p>
<pre class="code">/**
 * HMAC function using hash PECL (http://ca.php.net/manual/en/book.hash.php)
 *
 * @author Tommy Lacroix
 * @param string $stringToSign
 * @return string	base64 encoded hmac
 * @internal
 */
private function hmac_hash($stringToSign) {
	return base64_encode(pack('H*',hash_hmac('sha1', $stringToSign, $this-&gt;secretKey)));
}	

/**
 * HMAC function using mhash (http://ca.php.net/manual/en/book.hash.php)
 *
 * @author Tommy Lacroix
 * @param string $stringToSign
 * @return string	base64 encoded hmac
 * @internal
 */
private function hmac_mhash($stringToSign) {
	return base64_encode(mhash(MHASH_SHA1, $stringToSign, $this-&gt;secretKey));
}

/**
 * HMAC function using Lance's function (http://ca.php.net/manual/en/function.mhash.php, see comments)
 *
 * @author Tommy Lacroix
 * @param string $stringToSign
 * @return string	base64 encoded hmac
 * @internal
 */
private function hmac_lance($stringToSign) {
    $b = 64;
	if (strlen($this-&gt;secretKey) &gt; $b) {
		$key = pack("H*",sha1($this-&gt;secretKey));
	} else {
		$key = $this-&gt;secretKey;
	}
	$key  = str_pad($key, $b, chr(0x00));
	$ipad = str_pad('', $b, chr(0x36));
	$opad = str_pad('', $b, chr(0x5c));
	$k_ipad = $key ^ $ipad ;
	$k_opad = $key ^ $opad;

	return base64_encode(pack('H*', sha1($k_opad  . pack("H*",sha1($k_ipad . $stringToSign)))));
}</pre>
<h3>HTTP_Request replacement</h3>
<p>I replaced the <code>HTTP_Request</code> dependency by two potential routines: cURL and URL wrappers.</p>
<p>The code goes as follow:</p>
<pre class="code">/**
 * Fetch with curl
 *
 * @author Tommy Lacroix
 * @param string 	$url
 * @param string 	$qs
 * @param bool		$post
 * @return array(output,httpCode)
 * @internal
 */
private function fetch_curl($url, $qs, $post) {
	$curl = curl_init();
	curl_setopt($curl, CURLOPT_RETURNTRANSFER, TRUE);
	curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, FALSE);
	curl_setopt($curl, CURLOPT_HEADER, false);
	curl_setopt($curl, CURLOPT_POST, $post);
	curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
	curl_setopt($curl, CURLOPT_FOLLOWLOCATION, false);
	curl_setopt($curl, CURLOPT_URL, $url . (!$post ? '?'.$qs : ''));
	if ($post) {
		curl_setopt($curl, CURLOPT_POSTFIELDS, $qs);
	}

	// Execute
	$output = array();
	$output[0] = curl_exec($curl);
	$output[1] = curl_getinfo($curl,CURLINFO_HTTP_CODE);

	// Close handle
	curl_close($curl);

	return $output;
}

/**
 * Fetch with urlwrappers
 *
 * @author Tommy Lacroix
 * @param string 	$url
 * @param string 	$qs
 * @param bool		$post
 * @return array(output,httpCode)
 * @internal
 */
private function fetch_urlwrappers($url, $qs, $post) {
	$output = array();

	if ($post) {
		$opts = array(
		  'http'=&gt;array(
		    'method'=&gt;"POST",
		    'header'=&gt;"Content-type: application/x-www-form-urlencoded\r\n" .
		              "Content-length: " . strlen($qs),
		    'content'=&gt;$qs
		  )
		);
		$context = stream_context_create($opts);
	} else {
		$opts = array(
		  'http'=&gt;array(
		    'method'=&gt;"GET"
		  )
		);
		$context = stream_context_create($opts);
		$url .= '?'.$qs;
	}

	$output[0] = '';
	$f = @fopen($url,'r',null,$context);
	if (!$f) {
		$output[0] = '';
		$output[1] = 404;
		return $output;
	}
	while (!feof($f)) {
		$output[0] .= fread($f,1024);
	}
	$meta_data = stream_get_meta_data($f);
	if (preg_match('/^HTTP\/1\.[01] ([0-9]{3})/',$meta_data['wrapper_data'][0],$m)) {
		$output[1] = $m[1];
	} else {
		$output[1] = false;
	}
	fclose($f);

	$output[2] = 'wrappers';

	return $output;
}</pre>
<h3>Detection and conclusion</h3>
<p>Finally, the constructor includes a small detection mechanism that selects an HMAC and a fetching function:</p>
<pre class="code">/**
 * Constructor
 *
 * @author Justin@AWS
 * @author Tommy Lacroix
 * @param string $accessKey
 * @param string $secretKey
 * @param string $endpoint	http://queue.amazonaws.com
 * @param string $queueName	optional
 * @return SQSClient
 */
public function SQSClient($accessKey, $secretKey, $endpoint = 'http://queue.amazonaws.com', $queueName = '')
{
	.
	.
	.

	// Select fetch function
	// Call it with $this-&gt;{$this-&gt;fetchFunction}(...)
	if (function_exists('curl_init')) $this-&gt;fetchFunction = 'fetch_curl';
		else $this-&gt;fetchFunction = 'fetch_urlwrappers';

	// Select hash function
	// Call it with $this-&gt;{$this-&gt;hmacFunction}(...)
	if (function_exists('hash_hmac')) {
		$this-&gt;hmacFunction = 'hmac_hash';
	} else if (function_exists('mhash')) {
		$this-&gt;hmacFunction = 'hmac_mhash';
	} else if (class_exists('Crypt_HMAC')) {
		$this-&gt;hmacFunction = 'hmac_pear';
	} else {
		$this-&gt;hmacFunction = 'hmac_lance';
	}

	.
	.
	.
}</pre>
<p>The complete modified sqsClient package can be downloaded below. The original Justin@AWS package is <a title="Amazon SQS Sample in PHP by Justin@AWS" href="http://developer.amazonwebservices.com/connect/entry.jspa?externalID=1180" target="_blank">available here</a>.</p>
<p><a title="sqsClientPHP-2.1" href="http://www.tommylacroix.com/demos/sqsClientPHP-2.1/sqsClientPHP-2.1.zip"><img style="border: 0pt none ;" src="http://www.tommylacroix.com/img/icons/script_code.gif" alt="PHP Source" width="16" height="16" /></a> <a title="sqsClientPHP-2.1" href="http://www.tommylacroix.com/demos/sqsClientPHP-2.1/sqsClientPHP-2.1.zip" >sqsClientPHP-2.1.zip</a> <em>(11k)</em></p>
]]></content:encoded>
			<wfw:commentRss>http://www.tommylacroix.com/2008/07/11/super-charged-amazon-sqs-sample-in-php/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>PHP based FLV meta and cue points reader/writer</title>
		<link>http://www.tommylacroix.com/2008/07/04/a-php-tool-to-modify-an-flv-meta-and-cuepoints-on-the-fly/</link>
		<comments>http://www.tommylacroix.com/2008/07/04/a-php-tool-to-modify-an-flv-meta-and-cuepoints-on-the-fly/#comments</comments>
		<pubDate>Fri, 04 Jul 2008 21:30:26 +0000</pubDate>
		<dc:creator>tlacroix</dc:creator>
				<category><![CDATA[Flash]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[cue point]]></category>
		<category><![CDATA[flv]]></category>
		<category><![CDATA[meta data]]></category>
<category>cue point</category><category>flash</category><category>flv</category><category>meta data</category><category>php</category>
		<guid isPermaLink="false">http://www.tommylacroix.com/?p=63</guid>
		<description><![CDATA[Update (Dec 20, 2008): The AMF0Parser library has been updated to better handle broken AMF0 packets. The FLVInfo2 library PHPDoc has been updated.
Update (Aug 15, 2008): The AMF0Parser library has been updated to support the Date type in the metas, added among others by the &#8220;inlet media FLVTool2&#8243; tool.
Update (Jun 11, 2009): The project has [...]]]></description>
			<content:encoded><![CDATA[<p><img class="alignnone size-medium wp-image-68" style="float:left; margin: 0 8px 0 8px;" title="Flash Video" src="http://www.tommylacroix.com/wp-content/uploads/2008/07/64px-flashvideo.png" alt="" width="64" height="64" /><strong>Update (Dec 20, 2008): The AMF0Parser library has been updated to better handle broken AMF0 packets. The FLVInfo2 library PHPDoc has been updated.</strong></p>
<p><strong>Update (Aug 15, 2008): The AMF0Parser library has been updated to support the Date type in the metas, added among others by the &#8220;inlet media FLVTool2&#8243; tool.</strong></p>
<p><strong>Update (Jun 11, 2009): The project has been moved to </strong><a title="php-flvinfo at Google Code" href="http://code.google.com/p/php-flvinfo/" target="_blank"><strong>Google Code</strong></a><strong>. MP4Info, an F4V file (or more genericaly MP4 file) class is also available: check out <a title="PHP based MP4/F4V meta data reader" href="http://www.tommylacroix.com/2009/06/11/mp4-and-f4v-php-flash-video-meta-data-reader/" target="_self">blog post</a></strong><strong> and project at <a title="php-mp4info, at Google Code" href="http://code.google.com/p/php-mp4info/" target="_blank">Google Code</a></strong><strong>.</strong></p>
<p>If you&#8217;ve done Flash banners or micro-sites that embed video with cue points for synchronization before, you know that it&#8217;s a pain in the arse. There doesn&#8217;t seem to be any tool around to modify the damn points once the file is encoded, so you&#8217;ve got to re-encode the file over and over to have your things synced correctly.</p>
<p>If you&#8217;ve tried to reverse engineer the <a title="Flash Video, at Wikipedia" href="http://en.wikipedia.org/wiki/Flv" target="_blank">FLV format</a> before, you know that it&#8217;s a pain in the arse as well. The AMF0 format is anything but intuitive, and the documentation has been lacking for a long long time &#8212; although there&#8217;s apparently an SDK now. Luckily, there was <a title="OSFlash" href="http://osflash.org/" target="_blank">OSFlash</a>, and <a title="SabreAMF PHP Library, at Google Code" href="http://sabreamf.googlecode.com/" target="_blank">SabreAMF</a> and <a title="AMFPHP library" href="http://osflash.org/projects/amfphp" target="_blank">AMFPHP</a> that could be used as a documentation source.</p>
<p>If you recognize yourself, this might be your lucky day. After many hours of trial and error, I&#8217;ve finally been able to reverse engineer it, and to build a library that extract essential FLV information, reads the Meta, and allows you to write them back &#8212; including the cue points.</p>
<p><span id="more-63"></span></p>
<p>First, you&#8217;ll need to FLVInfo2 and the AMF0Parser libraries, below. The FLVInfo2 library has three main functions:</p>
<ul>
<li><code>getInfo</code>, which returns information gotten from analyzing the FLV file and it&#8217;s meta. Frame rate, bit rate, audio/video codec, width, height, etc.
<pre class="code">(
    [signature] =&gt; 1
    [hasVideo] =&gt; 1
    [hasAudio] =&gt; 1
    [minimalFlashVersion] =&gt; 8
    [video] =&gt; stdClass Object
        (
            [codec] =&gt; 4
            [width] =&gt; 320
            [height] =&gt; 240
            [keyframeRatio] =&gt; 0.0180537208278
            [keyframeEvery] =&gt; 55.3902439024
            [fps] =&gt; 15
            [bitrate] =&gt; 448
            [codecStr] =&gt; On2 VP6
        )

    [audio] =&gt; stdClass Object
        (
            [codec] =&gt; 2
            [frequency] =&gt; 22
            [depth] =&gt; 16
            [channels] =&gt; 2
            [bitrate] =&gt; 48
            [codecStr] =&gt; MP3
        )
)</pre>
</li>
<li><code>getMeta</code>, which returns the meta data, and the cue points
<pre class="code">Array
(
    [metas] =&gt; Array
        (
            [0] =&gt; Array
                (
                    [0] =&gt; onMetaData
                    [1] =&gt; Array
                        (
                            [duration] =&gt; 151.46
                            [width] =&gt; 320
                            [height] =&gt; 240
                            [videodatarate] =&gt; 500
                            [canSeekToEnd] =&gt; 1
                            [videocodecid] =&gt; 4
                            [audiodatarate] =&gt; 48
                            [audiocodecid] =&gt; 2
                            [framerate] =&gt; 15
                            [creationdate] =&gt; Tue Jun 17 13:06:15 2008
                            [Encoded_By] =&gt; orangetango Video Encoder
                            [Encoded_With] =&gt; orangetango Video Encoder
                            [metadatacreator] =&gt; orangetango FLV meta data writer
                        )
                )
        )
    [cuepoints] =&gt; Array
        (
            [0] =&gt; Array
                (
                    [0] =&gt; onCuePoint
                    [1] =&gt; Array
                        (
                            [name] =&gt; name1
                            [time] =&gt; 4.41
                            [type] =&gt; event
                        )
                )
            [1] =&gt; Array
                (
                    [0] =&gt; onCuePoint
                    [1] =&gt; Array
                        (
                            [name] =&gt; name1_end
                            [time] =&gt; 6.7
                            [type] =&gt; event
                        )
                )
        )
)</pre>
</li>
<li><code>rewriteMeta</code>, which inputs an FLV file, meta data and cue points, and outputs a new FLV file.</li>
</ul>
<p>As a practical example, you find the <code>rewriteTagsForFile</code> function in the <code>example.php</code> file, that rewrites the meta data and gets the cue points from a FLVCoreCuePoints XML file.</p>
<p><a title="FLVInfo2 class, reads and writes FLV meta data from a Flash Video File." href="http://www.tommylacroix.com/wp-content/uploads/2008/07/flvinfo2.phps" target="_blank"><span style="text-decoration: line-through;"><img style="border: 0pt none ;" src="http://www.tommylacroix.com/img/icons/script_code.gif" alt="PHP Source" width="16" height="16" /></span></a><span style="text-decoration: line-through;"> </span><a title="AMF0 Parser, reads and writes AMF0 encoded data" href="http://www.tommylacroix.com/wp-content/uploads/2008/07/flvinfo2.phps" target="_blank"><span style="text-decoration: line-through;">flvinfo2.php</span></a><span style="text-decoration: line-through;"> </span><em><span style="text-decoration: line-through;">(20k , updated Dec 20, 2008)</span></em></p>
<p><a title="FLVInfo2 class example" href="http://www.tommylacroix.com/wp-content/uploads/2008/07/amf0parser.phps" target="_blank"><span style="text-decoration: line-through;"><img style="border: 0pt none ;" src="http://www.tommylacroix.com/img/icons/script_code.gif" alt="PHP Source" width="16" height="16" /></span></a><span style="text-decoration: line-through;"> </span><a title="AMF0 Parser, reads and writes AMF0 encoded data" href="http://www.tommylacroix.com/wp-content/uploads/2008/07/amf0parser.phps" target="_blank"><span style="text-decoration: line-through;">AMF0Parser.php</span></a><span style="text-decoration: line-through;"> </span><em><span style="text-decoration: line-through;">(16k, updated Dec 20, 2008)</span></em></p>
<p><a title="FLVInfo2 class example" href="http://www.tommylacroix.com/wp-content/uploads/2008/07/example.phps" target="_blank"><span style="text-decoration: line-through;"><img style="border: 0pt none ;" src="http://www.tommylacroix.com/img/icons/script_code.gif" alt="PHP Source" width="16" height="16" /></span></a><span style="text-decoration: line-through;"> </span><a title="FLVInfo2 class example" href="http://www.tommylacroix.com/wp-content/uploads/2008/07/example.phps" target="_blank"><span style="text-decoration: line-through;">example.php</span></a><span style="text-decoration: line-through;"> </span><em><span style="text-decoration: line-through;">(12k)</span></em><a title="FLVInfo2 class example" href="http://www.tommylacroix.com/wp-content/uploads/2008/07/example.phps" target="_blank"><br />
</a></p>
<p><small>Flash Video logo found on <a title="Flash Video, at Wikipedia" href="http://en.wikipedia.org/wiki/Flash_Video">Wikipedia</a>.</small></p>
]]></content:encoded>
			<wfw:commentRss>http://www.tommylacroix.com/2008/07/04/a-php-tool-to-modify-an-flv-meta-and-cuepoints-on-the-fly/feed/</wfw:commentRss>
		<slash:comments>10</slash:comments>
		</item>
		<item>
		<title>CodeIgniter 1.6.x OpenID module</title>
		<link>http://www.tommylacroix.com/2008/06/25/codeigniter-16x-openid-module/</link>
		<comments>http://www.tommylacroix.com/2008/06/25/codeigniter-16x-openid-module/#comments</comments>
		<pubDate>Wed, 25 Jun 2008 12:42:22 +0000</pubDate>
		<dc:creator>tlacroix</dc:creator>
				<category><![CDATA[PHP]]></category>
		<category><![CDATA[Social networks]]></category>
		<category><![CDATA[authentication]]></category>
		<category><![CDATA[ciopenid]]></category>
		<category><![CDATA[codeigniter]]></category>
		<category><![CDATA[openid]]></category>
<category>authentication</category><category>ciopenid</category><category>codeigniter</category><category>openid</category><category>php</category>
		<guid isPermaLink="false">http://www.tommylacroix.com/?p=61</guid>
		<description><![CDATA[I got no IPod. I got no IPhone. But I&#8217;ve got an OpenID. I&#8217;m like a low class geek. But whatever. I&#8217;ve read a bit lately about OpenID and wanted to give it a try on one of my personal projects (CodeIgniter based, you bet).
I&#8217;ve stumbled upon Rémi Prévost &#8212; who&#8217;s blog a follow more [...]]]></description>
			<content:encoded><![CDATA[<p>I got no IPod. I got no IPhone. But I&#8217;ve got an OpenID. I&#8217;m like a low class geek. But whatever. I&#8217;ve read a bit lately about OpenID and wanted to give it a try on one of my personal projects (CodeIgniter based, you bet).</p>
<p>I&#8217;ve stumbled upon <a title="Rémi Prévost CIOpenID module" href="http://remiprevost.com/2007/10/codeigniter-openid-1a" target="_blank">Rémi Prévost &#8212; who&#8217;s blog a follow more or less consistently due to the lack of time &#8212; CIOpenID module</a>. It&#8217;s basically a CI embed of <a title="Janrain's PHP_OpenID" href="http://openidenabled.com/php-openid/" target="_blank">Janrain&#8217;s PHP_OpenID</a>.</p>
<p>His code is great and works well. But one thing I don&#8217;t like about it is that it requires a different bootstrap file (a modified version of <code>index.php</code>) and a somewhat hacked version of your typical <code>.htaccess</code> file. The reason is, CodeIgniter annihilates the <code>$_GET</code> variable during initialization, because GET queries aren&#8217;t secure (ok, this is overly simplified, but you get the idea).</p>
<p>Being who I am, and somewhat liking to reinvent the wheel during my free time, I used a different approach. Rather than using a different bootstrap file, I rather chose to use the <code>pre_system</code> hook.</p>
<p><span id="more-61"></span></p>
<p>The installation process is quite simple:</p>
<ol>
<li>Put my <code>ciopenid.php</code> library in your <code>/system/application/libraries</code> directory.</li>
<li>Create <code>/system/application/libraries/openid</code>, and unzip PHP_OpenID 2.x.x&#8217;s <code>Auth</code> directory in it.</li>
<li>Open your <code>/system/application/config/config.php</code> file, and make sure that hooks are enabled:
<pre class="code">$config['enable_hooks'] = TRUE;</pre>
</li>
<li>Open your <code>/system/application/config/hooks.php</code> file, and add the following lines:
<pre class="code">$hook['pre_system'][] = array(
   'class'    =&gt; 'ciopenid',
   'function' =&gt; 'pre_system_hook',
   'filename' =&gt; 'ciopenid.php',
   'filepath' =&gt; 'libraries',
   'params'   =&gt; null
   );</pre>
</li>
</ol>
<p>To make an authentication query to an OpenID URL in <code>$openid</code>:</p>
<pre class="code">$this-&gt;load-&gt;library('ciopenid');
try {
   $redirect_url = $this-&gt;ciopenid-&gt;authenticate($openid, site_url($this-&gt;uri-&gt;uri_string()));
   header("Location: ".$redirect_url);
} catch (Exception $e) {
   die('Error: '.$e-&gt;getMessage());
}</pre>
<p>And to get the OpenID query response:</p>
<pre class="code">$this-&gt;load-&gt;library('ciopenid');
if (isset($this-&gt;ciopenid-&gt;response)) {
   switch ($this-&gt;ciopenid-&gt;response-&gt;status) {
      case Auth_OpenID_SUCCESS:
         $openid = $this-&gt;ciopenid-&gt;response-&gt;endpoint-&gt;claimed_id;
         print 'Success: '.$openid;
         break;
      case Auth_OpenID_CANCEL:
         print 'User cancelled';
         break;
      default:
         print 'Internal error';
         break;
   }
}</pre>
<p>That&#8217;s it! Ooops, here&#8217;s the file&#8230;</p>
<p><a href="http://www.tommylacroix.com/wp-content/uploads/2008/06/ciopenid.zip"><img style="border: 0pt none;" src="http://www.tommylacroix.com/img/icons/script_code.gif" alt="PHP Source (zipped)" width="16" height="16" /></a> <a title="CIOpenID library, 1.0" href="http://www.tommylacroix.com/wp-content/uploads/2008/06/ciopenid.zip">ciopenid.zip (3k)<br />
</a></p>
<p>Enjoy!</p>
]]></content:encoded>
			<wfw:commentRss>http://www.tommylacroix.com/2008/06/25/codeigniter-16x-openid-module/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>Usability review of the week (April 4th, 08)</title>
		<link>http://www.tommylacroix.com/2008/04/04/usability-review-week-14/</link>
		<comments>http://www.tommylacroix.com/2008/04/04/usability-review-week-14/#comments</comments>
		<pubDate>Sat, 05 Apr 2008 01:53:41 +0000</pubDate>
		<dc:creator>tlacroix</dc:creator>
				<category><![CDATA[Usability]]></category>
		<category><![CDATA[amazon]]></category>
		<category><![CDATA[html emails]]></category>
		<category><![CDATA[text emails]]></category>
		<category><![CDATA[web design]]></category>
<category>amazon</category><category>usability</category><category>web design</category>
		<guid isPermaLink="false">http://www.tommylacroix.com/2008/04/04/usability-review-week-14/</guid>
		<description><![CDATA[Getting back to good habits, here&#8217;s another issue of the usability review of the week. I should actually be called usability review of the day, as all the good stuff I came across was published on April 3rd.

Big impact, small changes on Amazon (04/03/2008)
Effective text only emails (04/03/2008)
A Refreshing Take on Usability (04/03/2008)

Shall you stumble [...]]]></description>
			<content:encoded><![CDATA[<p>Getting back to good habits, here&#8217;s another issue of the usability review of the week. I should actually be called usability review of the day, as all the good stuff I came across was published on April 3rd.</p>
<ul>
<li>Big impact, small changes on Amazon (04/03/2008)</li>
<li>Effective text only emails (04/03/2008)</li>
<li>A Refreshing Take on Usability (04/03/2008)</li>
</ul>
<p>Shall you stumble on great usability pages, feel free to leave me a comment and I&#8217;ll give it a look.<br />
<span id="more-60"></span><br />
</p>
<h2>Big impact, small changes on Amazon (April 3rd, 2008)</h2>
<p>You probably didn&#8217;t notice unless you&#8217;re a savvy Amazon shopper (whether you shop for books or for usability practices) , but Amazon recently made subtle but great changes to the design. They improved readability of key information elements, and redesigned the up-selling space. Check the link below for a great before/after comparison (that I didn&#8217;t feel comfortable to rip even if I was going to credit the author).</p>
<p>Via: <a href="http://www.grokdotcom.com/2008/04/03/amazon-usability-testing/" title="Small changes, big impact on Amazon, from Future Now's blog" target="_blank">Future Now&#8217;s blog</a></p>
<h2>Effective text only emails  (April 3rd, 2008)</h2>
<p>Text emails don&#8217;t have to be bad and boring. Okay, they&#8217;ve got to be boring. But still, when HTML emails aren&#8217;t an option, or even just to provide a text alternative for all these new non-HTML cell phone email readers out there, there are ways to make text emails look better:</p>
<ul>
<li>Put links on new lines</li>
<li>Uses lists with asterisks as bullets</li>
<li>Try to keep it short</li>
<li>If you can&#8217;t keep it short, divide in sections</li>
<li>Call to action</li>
</ul>
<p>Via: <a href="http://shapeshed.com/journal/effective_text_only_emails/" title="Effective text only emails, on Shape Shed" target="_blank">Shape Shed</a></p>
<h2>A Refreshing Take on Usability (April 3rd, 2008)</h2>
<p>This is more a funny heads up than a news, but it shows how well one can use available information to improve user experience.</p>
<p>Via:  <a href="http://flux88.com/ARefreshingTakeOnUsability.aspx" title="A Refreshing Take on Usability, on B#" target="_blank">B#</a></p>
]]></content:encoded>
			<wfw:commentRss>http://www.tommylacroix.com/2008/04/04/usability-review-week-14/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
