<?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"
	>

<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>
	<pubDate>Fri, 15 Aug 2008 19:01:36 +0000</pubDate>
	<generator>http://wordpress.org/?v=2.6.1</generator>
	<language>en</language>
			<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>
		</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="FLVInfo2 class example" href="http://www.tommylacroix.com/wp-content/uploads/2008/07/example.phps" target="_blank"><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" target="_self">sqsClientPHP-2.1.zip</a> <em>(11k)</em><a title="FLVInfo2 class example" href="http://www.tommylacroix.com/wp-content/uploads/2008/07/example.phps" target="_blank"><br />
</a></p>
]]></content:encoded>
			<wfw:commentRss>http://www.tommylacroix.com/2008/07/11/super-charged-amazon-sqs-sample-in-php/feed/</wfw:commentRss>
		</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 (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.
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 [...]]]></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 (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>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"><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="AMF0 Parser, reads and writes AMF0 encoded data" href="http://www.tommylacroix.com/wp-content/uploads/2008/07/flvinfo2.phps" target="_blank">flvinfo2.php</a> <em>(175k)</em></p>
<p><a title="FLVInfo2 class example" href="http://www.tommylacroix.com/wp-content/uploads/2008/07/amf0parser.phps" target="_blank"><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="AMF0 Parser, reads and writes AMF0 encoded data" href="http://www.tommylacroix.com/wp-content/uploads/2008/07/amf0parser.phps" target="_blank">AMF0Parser.php</a> <em>(96k, updated Aug 15, 2008)</em></p>
<p><a title="FLVInfo2 class example" href="http://www.tommylacroix.com/wp-content/uploads/2008/07/example.phps" target="_blank"><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="FLVInfo2 class example" href="http://www.tommylacroix.com/wp-content/uploads/2008/07/example.phps" target="_blank">example.php</a> <em>(12k)</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>
		</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>
		</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>
		</item>
		<item>
		<title>Yet another Facebook conspiracy theory</title>
		<link>http://www.tommylacroix.com/2008/03/30/yet-another-facebook-conspiracy-theory/</link>
		<comments>http://www.tommylacroix.com/2008/03/30/yet-another-facebook-conspiracy-theory/#comments</comments>
		<pubDate>Sun, 30 Mar 2008 15:56:13 +0000</pubDate>
		<dc:creator>tlacroix</dc:creator>
		
		<category><![CDATA[Marketing]]></category>

		<category><![CDATA[Social networks]]></category>

		<category><![CDATA[conspiracy]]></category>

		<category><![CDATA[ethics]]></category>

		<category><![CDATA[facebook]]></category>

		<category><![CDATA[privacy]]></category>

		<category><![CDATA[web 2.0]]></category>
<category>conspiracy</category><category>ethics</category><category>facebook</category><category>marketing</category><category>privacy</category><category>social networks</category><category>web 2.0</category>
		<guid isPermaLink="false">http://www.tommylacroix.com/2008/03/30/yet-another-facebook-conspiracy-theory/</guid>
		<description><![CDATA[Long time no write, I have been really busy in the last two weeks. Big news coming professionally. I&#8217;ll keep you posted on this.
Schedule aside, there&#8217;s an idea that has been on my mind for quite some time. Actually, since I saw the What happens in the Facebook stays in the Facebook Flash presentation, and [...]]]></description>
			<content:encoded><![CDATA[<p><img src="http://www.tommylacroix.com/wp-content/uploads/2008/03/facebook.gif" alt="Facebook Logo" style="float: right; margin-left: 10px; margin-bottom: 10px" />Long time no write, I have been really busy in the last two weeks. Big news coming professionally. I&#8217;ll keep you posted on this.</p>
<p>Schedule aside, there&#8217;s an idea that has been on my mind for quite some time. Actually, since I saw the <a href="http://albumoftheday.com/facebook/" title="What happens in the Facebook stays in the Facebook" target="_blank">What happens in the Facebook stays in the Facebook</a> Flash presentation, and after my advertising teacher told us something like : &#8220;Advertising on the web is great, because you can track the viewers behavior, including pre and post viewing&#8221;.</p>
<p><span id="more-58"></span></p>
<h3>AdServer conspiracy</h3>
<p>Well, Facebook knows a lot about you, and in advertising, the more you know about your viewer, the more relevant the ads you present him can be. Now, Facebook has an internal advertising platform, <a href="http://www.facebook.com/ads/" title="Facebook Social Ads" target="_blank">Facebook Social Ads</a>, based on this. Nothing external, such as AdSense, yet.</p>
<p>But lets imagine that Facebook launches an external AdServer and builds a big network of small advertisers, like Google AdSense, or a small network of large advertisers.</p>
<p>First, as Facebook knows a lot about you, it simply has to set a cookie in your browser when you go on your profile, and reuse the cookie when displaying a banner on newyorktimes.com. The AdServer would then know that I&#8217;m a 27 years old male in a relationship with a university degree.</p>
<h3>The force</h3>
<p>This could lead to very interesting possibilities:</p>
<ul>
<li><strong>Segmented Ad Performance Calculation </strong>: Does you ad under-performs except with single gay females with a college degree? You can&#8217;t know now, but a Facebook AdServer could tell you.</li>
<li><strong>Segmented Ad Presentation</strong>: Only present the ad to your target segments. So you could have a Monster ad presented only to unemployed viewers and viewers who didn&#8217;t update their work profile for the past 2 years.</li>
<li><strong>Segmented Ad Selection</strong> : Present an ad more specific to the viewer&#8217;s segment. Creating various versions of the same banner is cheap, so if you can present a version more specific to a segment. So you could have an Obama ad targeted for:
<ul>
<li>Afro-Americans</li>
<li>Hispanic-Americans</li>
<li>The bunch</li>
</ul>
</li>
</ul>
<h3>The ethics</h3>
<p>Sure, this raises many ethical questions, as it could be misused by the wrong people (you know, the bad guys in 24 and in Michael Moore&#8217;s films).</p>
<p>I remember (or I think I do remember &#8212; please let me know if I&#8217;m misquoting) that, while I was working at ZeroKnowledge Systems way back, Adam Shostack&#8217;s moto was: &#8220;Someday, soon enough, all databases will be interconnected&#8221;.</p>
<p>That&#8217;s why ZeroKnowledge developed their Freedom platform, allowing you to browse anonymously. But nobody cared at the time: querying a 500MB database took a few seconds on cluster, so no one could realistically store so much information in a usable way. <a href="http://slashdot.org/yro/01/10/04/1526256.shtml" title=" ZeroKnowledge to Discontinue Anonymity Service, on Slashdot" target="_blank">That&#8217;s why ZK&#8217;s great idea and platform never really worked, business-wise</a>. Too much, too soon.</p>
<p>But in the 2008 world were Google and Yahoo crawl &#8212; and even cache &#8212; a huge portion of the web, where 25% of adults in Quebec have a Facebook profile, and where interconnecting platforms is easier than ever with all the APIs around, this could be up and running tomorrow morning. Too little, too late?</p>
<p>Big brother is watching us.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.tommylacroix.com/2008/03/30/yet-another-facebook-conspiracy-theory/feed/</wfw:commentRss>
		</item>
		<item>
		<title>Saturday morning: Bunker game</title>
		<link>http://www.tommylacroix.com/2008/03/15/bunker-game/</link>
		<comments>http://www.tommylacroix.com/2008/03/15/bunker-game/#comments</comments>
		<pubDate>Sat, 15 Mar 2008 14:10:25 +0000</pubDate>
		<dc:creator>tlacroix</dc:creator>
		
		<category><![CDATA[Funny stuff]]></category>

		<category><![CDATA[bunker]]></category>

		<category><![CDATA[flash game]]></category>

		<category><![CDATA[game]]></category>

		<category><![CDATA[saturday morning]]></category>

		<guid isPermaLink="false">http://www.tommylacroix.com/2008/03/15/bunker-game/</guid>
		<description><![CDATA[In this saturday morning, I’m pasting my copy, sipping my coffee and sniffing around other peoples blog looking for gems.
Well, no gems found this morning, only a little single-handed pleasure Flash game called Bunker. Free advice: don&#8217;t buy the shitty 400$ shot gun, it really sucks.
]]></description>
			<content:encoded><![CDATA[<p><img src="http://www.tommylacroix.com/wp-content/uploads/2008/03/bunker-game.gif" alt="Bunker Game" style="border: 1px solid black; float: left; margin-right: 5px; margin-bottom: 5px" />In this saturday morning, I’m pasting my copy, sipping my coffee and sniffing around other peoples blog looking for gems.</p>
<p>Well, no gems found this morning, only a little single-handed pleasure Flash game called <a href="http://www.pagez.co.uk/game8/Bunker.htm" title="Bunker game" target="_blank">Bunker</a>. Free advice: don&#8217;t buy the shitty 400$ shot gun, it really sucks.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.tommylacroix.com/2008/03/15/bunker-game/feed/</wfw:commentRss>
		</item>
		<item>
		<title>Usability review of the week (March 14th, 08)</title>
		<link>http://www.tommylacroix.com/2008/03/14/usability-review-week-11/</link>
		<comments>http://www.tommylacroix.com/2008/03/14/usability-review-week-11/#comments</comments>
		<pubDate>Fri, 14 Mar 2008 19:06:06 +0000</pubDate>
		<dc:creator>tlacroix</dc:creator>
		
		<category><![CDATA[Usability]]></category>

		<category><![CDATA[ecommerce]]></category>

		<category><![CDATA[report]]></category>

		<category><![CDATA[search engine]]></category>

		<category><![CDATA[shopping cart]]></category>

		<category><![CDATA[study]]></category>

		<category><![CDATA[weekly review]]></category>
<category>google</category><category>usability</category><category>web design</category>
		<guid isPermaLink="false">http://www.tommylacroix.com/2008/03/14/usability-review-week-11/</guid>
		<description><![CDATA[Another crazy week, and not so much good usability articles appeared on my radar. Yet, there&#8217;s a few, and here they are.

Is Customer Experience Recession-Proof? (03/14/2008)
eCommerce Usability Review: Advanced Search Pages (03/13/2008)
11 Ways to Fill Your Shopper&#8217;s Cart (03/12/2008)
Google to start to implement site performance metrics in SEM Quality Score (03/07/2008)


Is Customer Experience Recession-Proof?
Apparently, when [...]]]></description>
			<content:encoded><![CDATA[<p>Another crazy week, and not so much good usability articles appeared on my radar. Yet, there&#8217;s a few, and here they are.</p>
<ul>
<li>Is Customer Experience Recession-Proof? (03/14/2008)</li>
<li>eCommerce Usability Review: Advanced Search Pages (03/13/2008)</li>
<li>11 Ways to Fill Your Shopper&#8217;s Cart (03/12/2008)</li>
<li>Google to start to implement site performance metrics in SEM Quality Score (03/07/2008)</li>
</ul>
<p><span id="more-55"></span><br />
<h2>Is Customer Experience Recession-Proof?</h2>
<p>Apparently, when consumer spending drops, the fight over the remaining dollars intensifies. A <a href="http://www.forrester.com/Research/Document/Excerpt/0,7211,45134,00.html" title="Customer Experience Spending Intensifies In 2008, by Forrester" target="_blank">new report from Forrester</a> goes in that direction. According to the summary, &#8220;more than 80% of respondents said that improving the usability, usefulness and enjoyability of the online experience is more important this year.&#8221; Respondents forecast increased spending in Web Analytics (68%), Usability (53%) and Behavioral Research (51%).</p>
<p>Via: <a href="http://blogs.cioinsight.com/research_central/content001/no_recession_for_web_site_improvement_1.html" title="Is Customer Experience Recession-Proof?, by Allan Alter from CIO Insight">Research Central @ CIO Insight</a></p>
<h2>eCommerce Usability Review: Advanced Search Pages</h2>
<p>Yesterday, Varien published an <a href="http://www.varien.com/miscellaneous/ecommerce-usability-review-advanced-search-pages/" title="Usability review on advanced search page attributes" target="_blank">advanced search page attributes review</a>, that is somewhat a sequel to  <a href="http://www.varien.com/ecommerce/ecommerce-product-search-web-gallery/" title="Usability review on search page attributes" target="_blank">usability review on search page attributes</a>. Same concept as before, the folks take a couple of very good, good and bad pages to display what advanced search engine pages out there allow the user to do. I&#8217;m particularilly a fan of the &#8220;refine search&#8221; concept,<br />
as it is in harmony with the progressive disclosure principle.</p>
<p>Via: <a href="http://www.varien.com/miscellaneous/ecommerce-usability-review-advanced-search-pages/" title="Usability review on advanced search page attributes" target="_blank">Varien&#8217;s eCommerce Cache Blog</a></p>
<p>See also: <a href="http://www.varien.com/ecommerce/usability-gift-registries/" title="Usability of gift registries, at Varien" target="_blank">Usability of gift registries, also at Varien</a></p>
<h2>11 Ways to Fill Your Shopper&#8217;s Cart</h2>
<p>I liked this one as it helped complete my eCommerce site functionality checklist. Most of Stoney deGeyter&#8217;s tips are obvious if you already designed a few eCommerce sites (search engine, product comparison guides, product reviews, etc.), but the list is brief and well assembled, which makes it a good reference tool to save you some thinking time.</p>
<p>Via:  <a href="http://www.searchengineguide.com/stoney-degeyter/11-ways-fill-your-shoppers-cart.php" title="11 Ways to Fill Your Shopping Cart, at Search Engine Guide" target="_blank">Search Engine Guide</a></p>
<h2>Google to start to implement site performance metrics in SEM Quality Score</h2>
<p>Earlier this week, Google released a post indicating that they will start to consider the landing page load time in the calculation of the Quality Score. The guys at <a href="http://www.traffick.com/2008/03/site-performance-increasingly-important.asp" title="Site Performance Increasingly Important to SEM Performance, at Traffick" target="_blank">Traffick </a>see it as the beginning of a trend, and I tend to agree after reading <a href="http://www.seobythesea.com/?p=1014" title="Yahoo Automates Usability Consulting, at SEO by the SEA" target="_blank">Yahoo&#8217;s usability patent filling last week</a>.</p>
<p>Via: <a href="http://www.traffick.com/2008/03/site-performance-increasingly-important.asp" title="Site Performance Increasingly Important to SEM Performance, at Traffick" target="_blank">Traffick </a></p>
]]></content:encoded>
			<wfw:commentRss>http://www.tommylacroix.com/2008/03/14/usability-review-week-11/feed/</wfw:commentRss>
		</item>
		<item>
		<title>JavaScript database with CRUD interface: TaffyDB</title>
		<link>http://www.tommylacroix.com/2008/03/12/javascript-database-taffydb/</link>
		<comments>http://www.tommylacroix.com/2008/03/12/javascript-database-taffydb/#comments</comments>
		<pubDate>Wed, 12 Mar 2008 14:07:20 +0000</pubDate>
		<dc:creator>tlacroix</dc:creator>
		
		<category><![CDATA[Programming]]></category>

		<category><![CDATA[ajax]]></category>

		<category><![CDATA[database]]></category>

		<category><![CDATA[HTML]]></category>

		<category><![CDATA[javascript]]></category>
<category>AJAX</category><category>database</category><category>JavaScript</category>
		<guid isPermaLink="false">http://www.tommylacroix.com/2008/03/12/javascript-database-taffydb/</guid>
		<description><![CDATA[As the post title says, TaffyDB is a JavaScript database with a CRUD &#8212; Create, Read, Update, Delete &#8212; interface. Object oriented, under 10K and compatible with most AJAX frameworks around, TaffyDB is a cool tool to bring your AJAX applications to the next level.
The object usage is quite intuitive&#8230; see for yourself:
products.find({price:{lessthan:10},
   [...]]]></description>
			<content:encoded><![CDATA[<p>As the post title says, <a href="http://taffydb.com/" title="TaffyDB: A JavaScript database with a CRUD interface">TaffyDB</a> is a JavaScript database with a CRUD &#8212; Create, Read, Update, Delete &#8212; interface. Object oriented, under 10K and compatible with most AJAX frameworks around, <a href="http://taffydb.com/" title="TaffyDB: A JavaScript database with a CRUD interface">TaffyDB</a> is a cool tool to bring your AJAX applications to the next level.</p>
<p>The object usage is quite intuitive&#8230; see for yourself:</p>
<pre class="code">products.find({price:{lessthan:10},
              type:{not:"Book"}});

products.update({status:"NA"},
                {manufacturer:"XZYDesign"});

products.orderBy(
   ["type",{"price":"asce"},{"quantity":"asce"}]);</pre>
<p>I&#8217;m definitely going to find a use for this in a project soon. Meanwhile, I&#8217;m still messing with it to seize its full power. Enjoy&#8230;!</p>
]]></content:encoded>
			<wfw:commentRss>http://www.tommylacroix.com/2008/03/12/javascript-database-taffydb/feed/</wfw:commentRss>
		</item>
		<item>
		<title>Usability review of the week (March 7th, 08)</title>
		<link>http://www.tommylacroix.com/2008/03/07/usability-review-week-10/</link>
		<comments>http://www.tommylacroix.com/2008/03/07/usability-review-week-10/#comments</comments>
		<pubDate>Fri, 07 Mar 2008 13:06:19 +0000</pubDate>
		<dc:creator>tlacroix</dc:creator>
		
		<category><![CDATA[Usability]]></category>

		<category><![CDATA[css framework]]></category>

		<category><![CDATA[jakob nielsen]]></category>

		<category><![CDATA[roi]]></category>

		<category><![CDATA[study]]></category>

		<category><![CDATA[weekly review]]></category>
<category>css</category><category>framework</category><category>html</category><category>seo</category><category>usability</category><category>web design</category>
		<guid isPermaLink="false">http://www.tommylacroix.com/2008/03/07/usability-review-week-10/</guid>
		<description><![CDATA[Here&#8217;s what I stumbled upon this week, on the web, about usability. The reviews are quite brief as this has been a crazy week for me.

Jakob Nielsen&#8217;s reports usability ROI decline(03/04/2008)
Yahoo Automates Usability Consulting (03/03/2008)
Study: Introductory Paragraphs and Tabs Don’t Aid Reading Comprehension Online (05/03/2008)
Measuring satisfaction: Beyond the usability questionnaire (03/03/2008)


Jakob Nielsen&#8217;s reports usability ROI [...]]]></description>
			<content:encoded><![CDATA[<p>Here&#8217;s what I stumbled upon this week, on the web, about usability. The reviews are quite brief as this has been a crazy week for me.</p>
<ul>
<li>Jakob Nielsen&#8217;s reports usability ROI decline(03/04/2008)</li>
<li>Yahoo Automates Usability Consulting (03/03/2008)</li>
<li>Study: Introductory Paragraphs and Tabs Don’t Aid Reading Comprehension Online (05/03/2008)</li>
<li>Measuring satisfaction: Beyond the usability questionnaire (03/03/2008)</li>
</ul>
<p><span id="more-53"></span></p>
<h2>Jakob Nielsen&#8217;s reports usability ROI decline</h2>
<p><a href="http://www.useit.com/" title="Jakob Nielsen UseIt.com" target="_blank">Jakob Nielsen</a> reports that <a href="http://www.useit.com/alertbox/roi.html" title="Average usability ROI of web sites, Jaokb Nielsen, 2008" target="_blank">average the usability ROI of web sites</a> declined from 135% (2002) to 83% (2008). Nielson gives a few explanations to the decline. Two of them are :</p>
<blockquote><p>We have now harvested most of the low-hanging fruit from the truly horrible websites that dominated the lost decade of Web usability (approximately 1993–2003). In those early years, Web design was abominable — think splash screens, search that didn’t find anything, bloated graphics everywhere. The only good thing about these early designs was that they were so bad that it was easy for usability people to be heroes: even the smallest study would inevitably reveal several immense opportunities for improvement.</p></blockquote>
<p>Via: <a href="http://www.littlespringsdesign.com/blog/2008/03/04/usability-roi/Little" title="Usability ROI, on Little Spring Design">Little Sprint Design</a></p>
<h2>Yahoo Automates Usability Consulting</h2>
<p>A recent <a href="http://www.yahoo.com" title="Yahoo" target="_blank">Yahoo</a> <a href="http://appft1.uspto.gov/netacgi/nph-Parser?Sect1=PTO2&amp;Sect2=HITOFF&amp;u=%2Fnetahtml%2FPTO%2Fsearch-adv.html&amp;r=1&amp;p=1&amp;f=G&amp;l=50&amp;d=PG01&amp;S1=20080040195.PGNR.&amp;OS=dn/20080040195&amp;RS=DN/20080040195" title="Quantitative Analysis of Web Page Clutter that Accounts for Subjective Preferences" target="_blank">patent application</a> lists usability factors a search engine could use to determine how usable a web page is.  Why would Yahoo be interested in knowing?  Quoting the authors of the document :</p>
<blockquote><p>It can be important to make web pages easy and pleasing to use, which can be particularly important for web pages it is desired to monetize.</p>
<p>This may include, for example, advertisement-containing web pages (of a so-called “web portal,” for example), for which an advertiser pays money when a user views the web page and activates a link of the advertisement.</p>
<p>If such web pages are not easy and pleasing to use, the money-making potential of those web pages can be jeopardized. One conventional indication of whether a web page is easy and pleasing to use is called “clutter.”</p></blockquote>
<p>I personally crave for a usability test engine to replace my old boring evaluation checklist. Next step would be a Google Analytics module that would analyze the browsing patterns.</p>
<p>Via: <a href="http://www.seobythesea.com/?p=1014" title="Yahoo Automates Usability Consulting, by SEO by the SEA" target="_blank">SEO by the SEA</a></p>
<h2>Study: Introductory Paragraphs and Tabs Don’t Aid Reading Comprehension Online</h2>
<p>A recent study by the University of Washington analyzes the impact of introductory paragraphs (presence of the paragraph, and presence of links in the paragraph), and tabbed navigation.</p>
<p>Cutting to the chase, users apparently like tabs, but introductory text might have no effect on comprehension. Anchored text is helps increasing comprehension &#8212; especially on how the concepts relate to each other &#8212; but decreases user satisfaction, especially when there&#8217;s no tabbed navigation present. I strongly encourage you to spend 5 minutes to read <a href="http://faithfulweb.wordpress.com/2008/03/05/usability-tabs-links/" title="Study: Introductory Paragraphs and Tabs Don’t Aid Reading Comprehension Online, at Faithful Web" target="_blank">Faithful Web summary</a>.<br />
Via: <a href="http://faithfulweb.wordpress.com/2008/03/05/usability-tabs-links/" title="Study: Introductory Paragraphs and Tabs Don’t Aid Reading Comprehension Online, from FaithfulWeb" target="_blank">Faithful Web</a></p>
<h2>Measuring satisfaction: Beyond the usability questionnaire</h2>
<p>David Travis from UserFocus describes the criticism-reluctancy problem in rational usability testing, and provides a method to overcome this problem.</p>
<p>This is very similar to a few focus group interview techniques used in marketing research, but applied to usability testing.</p>
<p>Via: <a href="http://www.userfocus.co.uk/articles/satisfaction.html" title="Measuring satisfaction: Beyond the usability questionnaire, from UserFocus" target="_blank">UserFocus</a></p>
]]></content:encoded>
			<wfw:commentRss>http://www.tommylacroix.com/2008/03/07/usability-review-week-10/feed/</wfw:commentRss>
		</item>
	</channel>
</rss>
