<?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>Brandon Checketts &#187; Programming</title> <atom:link href="http://www.brandonchecketts.com/archives/category/programming/feed" rel="self" type="application/rss+xml" /><link>http://www.brandonchecketts.com</link> <description>Web Programming, Linux System Administation, and other geeky stuff</description> <lastBuildDate>Sun, 25 Jul 2010 00:50:58 +0000</lastBuildDate> <generator>http://wordpress.org/?v=2.9.1</generator> <language>en</language> <sy:updatePeriod>hourly</sy:updatePeriod> <sy:updateFrequency>1</sy:updateFrequency> <item><title>Enabling HTTP Page Caching with PHP</title><link>http://www.brandonchecketts.com/archives/enabling-http-page-caching-with-php</link> <comments>http://www.brandonchecketts.com/archives/enabling-http-page-caching-with-php#comments</comments> <pubDate>Thu, 29 Apr 2010 16:54:30 +0000</pubDate> <dc:creator>Brandon</dc:creator> <category><![CDATA[PHP]]></category> <category><![CDATA[Programming]]></category> <category><![CDATA[Websites]]></category><guid
isPermaLink="false">http://www.brandonchecketts.com/?p=454</guid> <description><![CDATA[I&#8217;ve been doing a lot of work on BookScouter.com lately to reduce page load time and generally increase the performance of the website for both users and bots.  One of the tips that the load time analyzer points out is to enable an expiration time for static content.  That is easy enough for [...]]]></description> <content:encoded><![CDATA[<p>I&#8217;ve been doing a lot of work on <a
href="http://bookscouter.com/">BookScouter.com</a> lately to reduce page load time and generally increase the performance of the website for both users and bots.  One of the tips that the <a
href="https://addons.mozilla.org/en-US/firefox/addon/3371">load time analyzer</a> points out is to enable an expiration time for static content.  That is easy enough for images and such by using an Apache directive such as:</p><pre>
    ExpiresActive On
    ExpiresByType image/gif A2592000
    ExpiresByType image/jpg A2592000
    ExpiresByType image/png A2592000
</pre><p>But pages generated with PHP by default have the Pragma: no-cache header set, so that the users&#8217; browsers do not cache the content at all.  In most cases, even hitting the back button will generate another request to the server which must be completely processed by the script.  You may be able to cache some of the most intensive operations inside your script, but this solution will eliminate that request completely.  Simply add this code to the top of any page that contains semi-static content.  It effectively sets the page expiration time to one hour in the future.  So if a visitor hits the same URL within that hour, the page is served locally from their browser cache instead of making a trip to the server.  It also sends an HTTP 304 (Not Modified) response code if the user requests to reload the page within the specified time.  That may or may-not be desired based on your site.</p><pre>
$expire_time = 60*60; // One Hour
header('Expires: '.gmdate('D, d M Y H:i:s \G\M\T', time() + $expire_time));
header("Cache-Control: max-age={$expire_time}");
header('Last-Modified: '.gmdate('D, d M Y H:i:s \G\M\T', time()));
header('Pragma: public');

if ((!empty($_SERVER['HTTP_IF_MODIFIED_SINCE'])) &#038;&#038; (time() - strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) < = $expire_time)) {
    header('HTTP/1.1 304 Not Modified');
    exit;
}
</pre></pre> ]]></content:encoded> <wfw:commentRss>http://www.brandonchecketts.com/archives/enabling-http-page-caching-with-php/feed</wfw:commentRss> <slash:comments>0</slash:comments> </item> <item><title>Skipping the DROP TABLE, CREATE TABLE statements in a large mysqldump file.</title><link>http://www.brandonchecketts.com/archives/skipping-the-drop-table-create-table-statements</link> <comments>http://www.brandonchecketts.com/archives/skipping-the-drop-table-create-table-statements#comments</comments> <pubDate>Wed, 28 Apr 2010 14:03:52 +0000</pubDate> <dc:creator>Brandon</dc:creator> <category><![CDATA[General]]></category> <category><![CDATA[Linux System Administration]]></category> <category><![CDATA[MySQL]]></category> <category><![CDATA[Programming]]></category><guid
isPermaLink="false">http://www.brandonchecketts.com/?p=451</guid> <description><![CDATA[I have a large table of test data that I&#8217;m copying into some development environments.  I exported the table with a mysqldump which has a DROP TABLE and CREATE TABLE statements at the topDROP TABLE IF EXISTS `mytable`;
CREATE TABLE `mytable` (
`somecol` varchar(10) NOT NULL default '',
... other columns ...
[...]]]></description> <content:encoded><![CDATA[<p>I have a large table of test data that I&#8217;m copying into some development environments.  I exported the table with a mysqldump which has a DROP TABLE and CREATE TABLE statements at the top</p><pre>
DROP TABLE IF EXISTS `mytable`;
CREATE TABLE `mytable` (
  `somecol` varchar(10) NOT NULL default '',
   ... other columns ...
  PRIMARY KEY  (`somecol`),
  KEY `isbn10` (`somecol`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
</pre><p>The problem is that the developer has altered the table and re-importing the test data would undo those changes.  Editing the text file is impractical because of its size (500 MB gzipped).  So I came up with this workaround which just slightly alters the SQL using sed so that it doesn&#8217;t try to drop or recreate the table.  It comments out the DROP TABLE line, and creates the new table in the test database instead of the real database.</p><pre>
zcat bigfile.sql.gz |sed "s/DROP/-- DROP/"|sed "s/CREATE TABLE /CREATE TABLE test./"|mysql databasename
</pre>]]></content:encoded> <wfw:commentRss>http://www.brandonchecketts.com/archives/skipping-the-drop-table-create-table-statements/feed</wfw:commentRss> <slash:comments>2</slash:comments> </item> <item><title>Installing SVN and Trac on a CentOS 5 server</title><link>http://www.brandonchecketts.com/archives/installing-svn-and-trac-on-a-centos-5-server</link> <comments>http://www.brandonchecketts.com/archives/installing-svn-and-trac-on-a-centos-5-server#comments</comments> <pubDate>Fri, 19 Mar 2010 20:13:35 +0000</pubDate> <dc:creator>Brandon</dc:creator> <category><![CDATA[General]]></category> <category><![CDATA[Linux System Administration]]></category> <category><![CDATA[Programming]]></category><guid
isPermaLink="false">http://www.brandonchecketts.com/?p=442</guid> <description><![CDATA[Make sure that you have the RPMForge repository enabled.  Install Subversion, mod_dav_svn, and trac.  This will install a few required dependencies (ie: neon and some python utils)# yum install subversion mod_dav_svn mod_python tracCreate a directory for your repositories, and an initial repository for testing, and create your htpasswd file.  Then create a [...]]]></description> <content:encoded><![CDATA[<p>Make sure that you have the <a
href="https://rpmrepo.org/RPMforge/Using">RPMForge repository enabled</a>.  Install Subversion, mod_dav_svn, and trac.  This will install a few required dependencies (ie: neon and some python utils)</p><pre>
# yum install subversion mod_dav_svn mod_python trac
</pre><p>Create a directory for your repositories, and an initial repository for testing, and create your htpasswd file.  Then create a trac environment and set it up.</p><pre>
# mkdir /home/svn/
# svnadmin create testrepo
# chown -R apache:apache /home/svn/*
# htpasswd -c  /home/svn/.htpasswd brandon

#mkdir /home/trac/
# trac-admin /home/trac/ initenv
    ... answer questions as appropriate ...
# chown apache:apache /home/trac/*
# htpasswd -c  /home/svn/.htpasswd brandon
</pre><p>Add this to your Apache configuration in the relevant place (I like to put it under an SSL VirtualHost)</p><pre>
    &lt;Location /svn&gt;
        DAV svn
        SVNParentPath /home/svn/
        #SVNListParentPath on
        # Authentication
        AuthType Basic
        AuthName "RoundSphere SVN Repository"
        AuthUserFile /home/svn/.htpasswd
        Order deny,allow
        Require valid-user
    &lt;/Location&gt;
    &lt;Location /trac&gt;
        SetHandler mod_python
        PythonHandler trac.web.modpython_frontend
        PythonOption TracEnv /home/trac
        PythonOption TracUriRoot /trac
        # Authentication
        AuthType Basic
        AuthName “MyCompany Trac Environment"
        AuthUserFile /home/svn/.htpasswd
        Require valid-user
    &lt;/Location&gt;
</pre><p>Now test to make sure that you can view your test repository in a browser and that it prompts for a username and password as desired:</p><p>https://your-hostname/svn/testrepo/</p><p>You should retrieve a plain looking page that mentions the name of your repository and that it is at Revision 0</p><p>You should also be able to access your trac installation at</p><p>https://your-hostname/trac/</p><p>Customize your logo, change the home page, start making some tickets, using the wiki and get to work.</p> ]]></content:encoded> <wfw:commentRss>http://www.brandonchecketts.com/archives/installing-svn-and-trac-on-a-centos-5-server/feed</wfw:commentRss> <slash:comments>3</slash:comments> </item> <item><title>PHP Wrapper Class for a Read-only database</title><link>http://www.brandonchecketts.com/archives/php-wrapper-class-for-a-read-only-database</link> <comments>http://www.brandonchecketts.com/archives/php-wrapper-class-for-a-read-only-database#comments</comments> <pubDate>Tue, 05 Jan 2010 04:11:58 +0000</pubDate> <dc:creator>Brandon</dc:creator> <category><![CDATA[General]]></category> <category><![CDATA[Linux System Administration]]></category> <category><![CDATA[MySQL]]></category> <category><![CDATA[PHP]]></category> <category><![CDATA[Programming]]></category><guid
isPermaLink="false">http://www.brandonchecketts.com/?p=421</guid> <description><![CDATA[This is a pretty special case of a database wrapper class where I wanted to discard any updates to the database, but want SELECT queries to run against an alternative read-only database.  In this instance, I have a planned outage of a primary database server, but would like the public-facing websites and web services [...]]]></description> <content:encoded><![CDATA[<p>This is a pretty special case of a database wrapper class where I wanted to discard any updates to the database, but want SELECT queries to run against an alternative read-only database.  In this instance, I have a planned outage of a primary database server, but would like the public-facing websites and web services to remain as accessible as possible.</p><p>I wrote this quick database wrapper class that will pass all SELECT queries on to a local replica of the database, and silently discard any updates.   On this site almost all of the functionality still works, but it obviously isn&#8217;t saving and new information while the primary database is unavailable.</p><p>Here is my class.  This is intended as a wrapper to an ADOdb class, but it is generic enough that I think it would work for many other database abstraction functions as well.</p><pre>
class db_unavailable {
    var $readonly_db;

    function __construct($readonly_db)
    {
        $this->query_db = $readonly_db;
    }

    function query($sql)
    {
        $args = func_get_args();
        if (preg_match("#(INSERT INTO|REPLACE INTO|UPDATE|DELETE)#i", $args[0])) {
            // echo "Unable to do insert/replace/update/delete query: $sql\n";
            return true;
        } else {
            return call_user_func_array(array($this->readonly_db, 'query'), $args);
        }
    }

    function __call($function, $args)
    {
        return call_user_func_array(array($this->readonly_db, $function), $args);
    }
}
</pre><p>I simply create my $query_db object that points to the read-only database.  Then create my main $db object as a new db_unavailable() object.  Any select queries against $db will behave as they normally do, and data-modifying queries will be silently discarded.</p> ]]></content:encoded> <wfw:commentRss>http://www.brandonchecketts.com/archives/php-wrapper-class-for-a-read-only-database/feed</wfw:commentRss> <slash:comments>0</slash:comments> </item> <item><title>LUG Presentation on SQL Basics</title><link>http://www.brandonchecketts.com/archives/lug-presentation-on-sql-basics</link> <comments>http://www.brandonchecketts.com/archives/lug-presentation-on-sql-basics#comments</comments> <pubDate>Fri, 18 Dec 2009 03:52:59 +0000</pubDate> <dc:creator>Brandon</dc:creator> <category><![CDATA[General]]></category> <category><![CDATA[LUG]]></category> <category><![CDATA[MySQL]]></category> <category><![CDATA[PHP]]></category> <category><![CDATA[Programming]]></category><guid
isPermaLink="false">http://www.brandonchecketts.com/?p=416</guid> <description><![CDATA[I gave a presentation tonight at my local Linux Users Group meeting on SQL Basics.   I had a fun time preparing the presentation and made up a bunch of examples having to do with Santa&#8217;s database.
It started out with a simple table for kids who were either naughty or nice.  [...]]]></description> <content:encoded><![CDATA[<p>I gave a presentation tonight at my local <a
href="http://www.uga.edu/chugalug/">Linux Users Group</a> meeting on SQL Basics.   I had a fun time preparing the presentation and made up a bunch of examples having to do with Santa&#8217;s database.</p><p>It started out with a simple table for kids who were either naughty or nice.  We then added some reports to that.   Then imported kids&#8217; wish lists from CSV files.  From there we were able to generate some manufacturing reports for the workshop.</p><p>When we joined the wish list table with the kids table, we were then able to generate a sleigh-loading report which included only gifts for kids who had been good.   Then we got even more complicated and introduced several joins with some complicated mathematics to select gifts for kids within a certain radius from a given zip code.</p><p>The presentation is <a
href="/downloads/CHUGALUG Chrsitmas SQL.ppt">available for download here</a>.  And Brian recorded part of the presentation which is available to view <a
href="http://www.ustream.tv/recorded/3205751">on uStream.tv</a> or here. (We&#8217;re still experimenting with getting the video recording set up correctly)</p><p><object
classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" width="480" height="386" id="utv528934" name="utv_n_851113"><param
name="flashvars" value="loc=%2F&amp;autoplay=false&amp;vid=3205751" /><param
name="allowfullscreen" value="true" /><param
name="allowscriptaccess" value="always" /><param
name="src" value="http://www.ustream.tv/flash/video/3205751" /><embed
flashvars="loc=%2F&amp;autoplay=false&amp;vid=3205751" width="480" height="386" allowfullscreen="true" allowscriptaccess="always" id="utv528934" name="utv_n_851113" src="http://www.ustream.tv/flash/video/3205751" type="application/x-shockwave-flash" /></object></p> ]]></content:encoded> <wfw:commentRss>http://www.brandonchecketts.com/archives/lug-presentation-on-sql-basics/feed</wfw:commentRss> <slash:comments>0</slash:comments> </item> <item><title>PHP Code to Sign any Amazon API Requests</title><link>http://www.brandonchecketts.com/archives/php-code-to-sign-any-amazon-api-requests</link> <comments>http://www.brandonchecketts.com/archives/php-code-to-sign-any-amazon-api-requests#comments</comments> <pubDate>Wed, 01 Jul 2009 04:30:11 +0000</pubDate> <dc:creator>Brandon</dc:creator> <category><![CDATA[General]]></category> <category><![CDATA[Linux System Administration]]></category> <category><![CDATA[PHP]]></category> <category><![CDATA[Programming]]></category><guid
isPermaLink="false">http://www.brandonchecketts.com/?p=378</guid> <description><![CDATA[Starting next month, any requests to the Amazon Product Advertising API need to be cryptographically signed.  Amazon has given about three months notice and the deadline is quickly approaching.  I use the Amazon web services on several sites and came up a fairly generic way to convert an existing URL to a signed [...]]]></description> <content:encoded><![CDATA[<p>Starting next month, any requests to the Amazon Product Advertising API need to be cryptographically signed.  Amazon has given about three months notice and the deadline is quickly approaching.  I use the Amazon web services on several sites and came up a fairly generic way to convert an existing URL to a signed URL.  I&#8217;ve tested with several sites and a variety of functions, and this is working well for me so far:</p><pre>
function signAmazonUrl($url, $secret_key)
{
    $original_url = $url;

    // Decode anything already encoded
    $url = urldecode($url);

    // Parse the URL into $urlparts
    $urlparts       = parse_url($url);

    // Build $params with each name/value pair
    foreach (split('&#038;', $urlparts['query']) as $part) {
        if (strpos($part, '=')) {
            list($name, $value) = split('=', $part, 2);
        } else {
            $name = $part;
            $value = '';
        }
        $params[$name] = $value;
    }

    // Include a timestamp if none was provided
    if (empty($params['Timestamp'])) {
        $params['Timestamp'] = gmdate('Y-m-d\TH:i:s\Z');
    }

    // Sort the array by key
    ksort($params);

    // Build the canonical query string
    $canonical       = '';
    foreach ($params as $key =&gt; $val) {
        $canonical  .= "$key=".rawurlencode(utf8_encode($val))."&#038;";
    }
    // Remove the trailing ampersand
    $canonical       = preg_replace("/&#038;$/", '', $canonical);

    // Some common replacements and ones that Amazon specifically mentions
    $canonical       = str_replace(array(' ', '+', ',', ';'), array('%20', '%20', urlencode(','), urlencode(':')), $canonical);

    // Build the sign
    $string_to_sign             = "GET\n{$urlparts['host']}\n{$urlparts['path']}\n$canonical";
    // Calculate our actual signature and base64 encode it
    $signature            = base64_encode(hash_hmac('sha256', $string_to_sign, $secret_key, true));

    // Finally re-build the URL with the proper string and include the Signature
    $url = "{$urlparts['scheme']}://{$urlparts['host']}{$urlparts['path']}?$canonical&#038;Signature=".rawurlencode($signature);
    return $url;
}
</pre><p>To use it, just wrap your Amazon URL with the signAmazonUrl() function and pass it your original string and secret key as arguments.  As an example:</p><pre>
$xml = file_get_contents('http://webservices.amazon.com/onca/xml?some-parameters');
</pre><p>becomes</p><pre>
$xml = file_get_contents(signAmazonUrl('http://webservices.amazon.com/onca/xml?some-parameters', $secret_key));
</pre><p>Like most all of the variations of this, it does require the hash functions be installed to use the <a
href="http://us3.php.net/hash_hmac">hash_hmac()</a> function.  That function is generally available in PHP 5.1+.  Older versions will need to install it with Pecl.  I tried using a couple of versions that try to create the Hash in pure PHP code, but none worked and installing it via Pecl was pretty simple.</p><p>(Note that I&#8217;ve slightly revised this code a couple of times to fix small issues that have been noticed)</p> ]]></content:encoded> <wfw:commentRss>http://www.brandonchecketts.com/archives/php-code-to-sign-any-amazon-api-requests/feed</wfw:commentRss> <slash:comments>19</slash:comments> </item> <item><title>Array versus String in CURLOPT_POSTFIELDS</title><link>http://www.brandonchecketts.com/archives/array-versus-string-in-curlopt_postfields</link> <comments>http://www.brandonchecketts.com/archives/array-versus-string-in-curlopt_postfields#comments</comments> <pubDate>Fri, 29 May 2009 17:53:04 +0000</pubDate> <dc:creator>Brandon</dc:creator> <category><![CDATA[General]]></category> <category><![CDATA[PHP]]></category> <category><![CDATA[Programming]]></category><guid
isPermaLink="false">http://www.brandonchecketts.com/?p=362</guid> <description><![CDATA[The PHP Curl Documentation for CURLOPT_POSTFIELDS makes this note:This can either be passed as a urlencoded string like &#8216;para1=val1&#038;para2=val2&#038;&#8230;&#8217; or as an array with the field name as key and field data as value. If value  is an array, the Content-Type header will be set to multipart/form-data.I&#8217;ve always discounted the importance of that, and [...]]]></description> <content:encoded><![CDATA[<p>The <a
href="http://us2.php.net/curl_setopt">PHP Curl Documentation</a> for CURLOPT_POSTFIELDS makes this note:</p><blockquote><p> This can either be passed as a urlencoded string like &#8216;para1=val1&#038;para2=val2&#038;&#8230;&#8217; or as an array with the field name as key and field data as value. If value  is an array, the Content-Type header will be set to multipart/form-data.</p></blockquote><p>I&#8217;ve always discounted the importance of that, and in most cases it doesn&#8217;t generally matter.  The destination server and application likely know how to deal with both multipart/form-data and application/x-www-form-urlencoded equally well.  However, the data is passed in a much different way using these two different mechanisms.</p><h3>application/x-www-form-urlencoded</h3><p>application/x-www-form-urlencoded is what I generally think of when doing POST requests.  It is the default when you submit most forms on the web. It works by appending a blank line and then your urlencoded data to the end of the POST request.  It also sets the Content-Length header to the length of your data.   A request submitted with application/x-www-form-urlencoded looks like this (somewhat simplified):</p><pre>
POST /some-form.php HTTP/1.1
Host: www.brandonchecketts.com
Content-Length: 23
Content-Type: application/x-www-form-urlencoded

name=value&#038;name2=value2
</pre><h3>multipart/form-data</h3><p>multipart/form-data is much more complicated, but more flexible.  Its flexibility is required when uploading files.   It works in a manner similar to MIME types.  The HTTP Request looks like this (simpified):</p><pre>
POST / HTTP/1.1
Host: www.brandonchecketts.com
Content-Length: 244
Expect: 100-continue
Content-Type: multipart/form-data; boundary=----------------------------26bea3301273
</pre><p>And then subsequent packets are sent containing the actual data.  In my simple case with two name/value pairs, it looks like this:</p><pre>
HTTP/1.1 100 Continue
------------------------------26bea3301273
Content-Disposition: form-data; name="name"

value
------------------------------26bea3301273
Content-Disposition: form-data; name="name2"

value2
------------------------------26bea3301273--
</pre><h3>CURL usage</h3><p>So, when sending POST requests in PHP/cURL, it is important to urlencode it as a string first.</p><p>This will generate the multipart/form-data version</p><pre>
$data = array('name' => 'value', 'name2' => 'value2');
curl_setopt($curl_object, CURLOPT_POSTFIELDS,  $data)
</pre><p>And this simple change will ensure that it uses the application/x-www-form-urlencoded version:</p><pre>
$data = array('name' => 'value', 'name2' => 'value2');
$encoded = '';
foreach($data as $name => $value){
    $encoded .= urlencode($name).'='.urlencode($value).'&#038;';
}
// chop off the last ampersand
$encoded = substr($encoded, 0, strlen($encoded)-1);
curl_setopt($curl_object, CURLOPT_POSTFIELDS,  $encoded)
</pre>]]></content:encoded> <wfw:commentRss>http://www.brandonchecketts.com/archives/array-versus-string-in-curlopt_postfields/feed</wfw:commentRss> <slash:comments>0</slash:comments> </item> <item><title>KnitMeter is now a Facebook App</title><link>http://www.brandonchecketts.com/archives/knitmeter-is-now-a-facebook-app</link> <comments>http://www.brandonchecketts.com/archives/knitmeter-is-now-a-facebook-app#comments</comments> <pubDate>Fri, 29 May 2009 15:04:24 +0000</pubDate> <dc:creator>Brandon</dc:creator> <category><![CDATA[MySQL]]></category> <category><![CDATA[PHP]]></category> <category><![CDATA[Programming]]></category> <category><![CDATA[Websites]]></category><guid
isPermaLink="false">http://www.brandonchecketts.com/?p=360</guid> <description><![CDATA[KnitMeter.com is a site that I wrote quickly for my wife to keep track of how much she has knit.  It generate a little &#8216;widget&#8217; image that can be placed on blogs, forums, etc and says how many miles of yarn you have knit in some period.  The site has been live for [...]]]></description> <content:encoded><![CDATA[<p><a
href="http://knitmeter.com/">KnitMeter.com</a> is a site that I wrote quickly for my wife to keep track of how much she has knit.  It generate a little &#8216;widget&#8217; image that can be placed on blogs, forums, etc and says how many miles of yarn you have knit in some period.  The site has been live for about a year and a half now and has a couple thousand registered users.</p><p>I have been receiving an increasing number of requests to add a method for adding a KnitMeter it to Facebook.  I&#8217;ve experimented with a couple of other ideas on Facebook and found that it was pretty straightforward to write an app.   KnitMeter seems like a decent candidate for a social app, so I started working on it about a week ago.  And I&#8217;m happy to say that I just made the application live late last night.  It is available at <a
href="http://apps.facebook.com/knitmeter/">http://apps.facebook.com/knitmeter/</a>.</p><p>Features include:</p><ul><li>Ability to add projects and add knitted lengths to a project (or not)</li><li>Settings for inputting lengths in feet, yards, or meters</li><li>Display how much you&#8217;ve knit in feet, yards, meters, kilometers, or miles</li><li>When entering a new length, you can choose to have it publish a &#8217;story&#8217; on your profile page</li><li>You can add a tab on your profile page that shows each of your projects as well as a total</li><li>You can add a KnitMeter &#8216;box&#8217; to the side of your profile page, or on your &#8216;boxes&#8217; tab.</li></ul><p>I recreated the database from scratch and defined it a little better, so I have a little bit of work to do in migrating the existing site and database over to the new structure.  Once that is done users will be able to import their data from the existing KnitMeter.com by providing their email/password.</p> ]]></content:encoded> <wfw:commentRss>http://www.brandonchecketts.com/archives/knitmeter-is-now-a-facebook-app/feed</wfw:commentRss> <slash:comments>3</slash:comments> </item> <item><title>Synchronize Remote Memcached Clusters with memcache_sync</title><link>http://www.brandonchecketts.com/archives/memcache_sync</link> <comments>http://www.brandonchecketts.com/archives/memcache_sync#comments</comments> <pubDate>Wed, 13 May 2009 04:30:07 +0000</pubDate> <dc:creator>Brandon</dc:creator> <category><![CDATA[General]]></category> <category><![CDATA[Linux System Administration]]></category> <category><![CDATA[MySQL]]></category> <category><![CDATA[Programming]]></category><guid
isPermaLink="false">http://www.brandonchecketts.com/?p=356</guid> <description><![CDATA[The problem:  Servers in two separate geographic locations each have their own memcached cluster.  However, there doesn&#8217;t currently exist (that I know of) a good way to copy data from one cluster to the other cluster.
One possible solution is to configure the application to perform all write operations in both places.  [...]]]></description> <content:encoded><![CDATA[<p>The problem:  Servers in two separate geographic locations each have their own memcached cluster.  However, there doesn&#8217;t currently exist (that I know of) a good way to copy data from one cluster to the other cluster.</p><p>One possible solution is to configure the application to perform all write operations in both places.  However, each operation requires a round-trip response.  If the servers are separated by 50ms or more, doing several write operations causes a noticable delay.</p><p>The solution that I&#8217;ve come up with is a perl program that I&#8217;m calling memcache_sync.  It acts a bit like a proxy that asynchronously performs write operations on a remote cluster.   Each geographic location runs an instance of memcache_sync that emulates a memcached server.  You configure your application to write to the local memcache cluster, and also to the memcache_sync instance.  memcache_sync queues the request and immediately returns a SUCCESS message so that your application can continue doing its thing.  A separate thread then writes those queued operations to the remote cluster.</p><p>The result is two memcache clusters that are synchronized in near-real time, without any noticable delay in the application.</p><p>I&#8217;ve implemented &#8217;set&#8217; and &#8216;delete&#8217; operations thus far, since that is all that my application uses.  I&#8217;ve just started using this on a production environment and am watching to see how it holds up.  So far, it is behaving well.</p><p>The script is available <a
href="/downloads/memcache_sync.pl">here</a>.  I&#8217;m interested to see how much need there is for such a program.  I&#8217;d be happy to have input from others and in developing this into a more robust solution that works outside of my somewhat limited environment.</p> ]]></content:encoded> <wfw:commentRss>http://www.brandonchecketts.com/archives/memcache_sync/feed</wfw:commentRss> <slash:comments>1</slash:comments> </item> <item><title>What is in a gclid?</title><link>http://www.brandonchecketts.com/archives/what-is-in-a-gclid</link> <comments>http://www.brandonchecketts.com/archives/what-is-in-a-gclid#comments</comments> <pubDate>Wed, 15 Apr 2009 14:17:43 +0000</pubDate> <dc:creator>Brandon</dc:creator> <category><![CDATA[General]]></category> <category><![CDATA[Linux System Administration]]></category> <category><![CDATA[PHP]]></category> <category><![CDATA[Programming]]></category><guid
isPermaLink="false">http://www.brandonchecketts.com/?p=348</guid> <description><![CDATA[When you use auto-tagging with your Adwords campaign, all request that are generated by Google Adwords contain a ?glcid parameter in the Request.   Adwords uses this to  pass some information to Analytics for traffic analysis.
I was curious, about what data the gclid parameter contained.  My guess was that it contained some [...]]]></description> <content:encoded><![CDATA[<p>When you use auto-tagging with your Adwords campaign, all request that are generated by Google Adwords contain a ?glcid parameter in the Request.   Adwords uses this to  pass some information to Analytics for traffic analysis.</p><p>I was curious, about what data the gclid parameter contained.  My guess was that it contained some  encoded or encrypted information regarding the origin of the click, so I did some analysis on the clicks that I received.  Some discussion about it was available <a
href="http://stackoverflow.com/questions/365888/how-to-decode-google-gclids">on this post</a>.</p><p>I ended up writing a quick PHP script that parses through an Apache log file.  It finds requests that contain a gclid and then produces a report of which letters occur in which positions of the gclid.</p><p>The script is available for download <a
href="/downloads/gclid.php">here</a>, and it generates a report like this:</p><pre>
Found 32507 appropriate lines
Character  1 [ 1] C
Character  2 [ 8] IJKLMNOP
Character  3 [32] -CDGHKLOPSTWX_abefijmnqruvyz2367
Character  4 [64] -CDEFG0ABHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz123456789
Character  5 [32] -_0ghijklmnopqrstuvwxyz123456789
Character  6 [32] -IJKLMNOPYZ_abcdefopqrstuv456789
Character  7 [32] -CDGHKLOPSTWX_abefijmnqruvyz2367
Character  8 [64] -ABCDEFG0HIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz123456789
Character  9 [32] 0-_ghijklmnopqrstuvwxyz123456789
Character 10 [ 4] JZp5
Character 11 [ 8] IMQUYcgk
Character 12 [ 1] C
Character 13 [ 1] F
Character 14 [10] QRSUWYZcde
Character 15 [61] -ABCEFGHIJKLMNOPQRSTUVWXYZ_ab0cdefghiklmnopqrstuvwxy123456789
Character 16 [63] -ABCDEFGHIJKLMNOQRSTUVWXYZ_abcde0fghijklmnopqrstuvwxyz123456789
Character 17 [17] DFGHIQabgiknrsx57
Character 18 [ 4] AQgw
Character 19 [ 1] o
Character 20 [ 1] d
Character 21 [64] -ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwx0yz123456789
Character 22 [32] ABCDEFGHQRSTUVWXghijklmnwyz0x123
Character 23 [64] -ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuv0wxyz123456789
Character 24 [64] -ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrs0tuvwxyz123456789
Character 25 [62] 0-ABCDEFHIJKLMOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz123456789
Character 26 [ 4] AQgw
</pre><p>This makes it clear that the parameter has some structure, but I&#8217;m still no closer to determining what it contains.    Counting up the unique values, it would seem that they have about 95 bits of information available, which might be enough room to store everything it would need to know about the search that created it.    Based on the reporting details in Analytics, I would presume that it somehow contains at least the following information:</p><ul><li>Campaign (id)</li><li>Keyword (id)</li><li>Ad Variation (id)</li><li>Position</li></ul><p>I did some research by clicking an ad multiple times and examining the glcids for those:</p><pre>
        12345678901234567890123456
/?gclid=CNHz5eD_8pkCFRCdnAodzniYQg
/?gclid=CIX_u-X_8pkCFQKenAodlWprSg
/?gclid=CMyI_4OA85kCFRIhnAodc2_oRg
/?gclid=CO_0pYyA85kCFQghnAodDDpaRQ
/?gclid=CIXo9JeA85kCFRIhnAodc2_oRg
/?gclid=CLitgp2A85kCFQubnAod1nx7Qg
/?gclid=CN3_1aOA85kCFQghnAodDDpaRQ
/?gclid=CPyi1quA85kCFRabnAodWnZbRQ
/?gclid=COq-67OA85kCFRMhnAodyQvSRg
/?gclid=COOplrmA85kCFRCdnAodzniYQg
</pre><p>I  noticed that most of the characters which use 32-64 characters vary quite a bit except for character #9, which was always an 8, and character #10 which was a &#8216;p&#8217; for the first two clicks, and then a &#8216;5&#8242; for all subsequent clicks.  That likely has some significance, but I&#8217;m out of time for playing with it for now.</p><p>Hopefully the script and this basic analysis might be of use for somebody else to use in digging into it further.</p><p>One other thought that I had is that the data (or each field) is somehow encrypted and when you &#8216;link&#8217; your Analytics account to your Adwords account it shares the decryption key so that it can get at the detail.</p> ]]></content:encoded> <wfw:commentRss>http://www.brandonchecketts.com/archives/what-is-in-a-gclid/feed</wfw:commentRss> <slash:comments>3</slash:comments> </item> </channel> </rss>
<!-- Performance optimized by W3 Total Cache. Learn more: http://www.w3-edge.com/wordpress-plugins/

Minified using disk
Page Caching using disk (user agent is rejected)
Database Caching 3/12 queries in 0.139 seconds using disk

Served from: www.brandonchecketts.com @ 2010-09-10 16:09:35 -->