<?xml version="1.0" encoding="iso-8859-1"?><!-- generator="b2evolution/4.0.5" -->
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:admin="http://webns.net/mvcb/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:atom="http://www.w3.org/2005/Atom">
	<channel>
		<title>Robin Smidsr&#248;d</title>
		<link>http://blog.robin.smidsrod.no/</link>
		<atom:link rel="self" type="application/rss+xml" href="http://blog.robin.smidsrod.no/?tempskin=_rss2" />
		<description>Bits and pieces from the world of Robin Smidsr&#248;d</description>
		<language>en-US</language>
		<docs>http://blogs.law.harvard.edu/tech/rss</docs>
		<admin:generatorAgent rdf:resource="http://b2evolution.net/?v=4.0.5"/>
		<ttl>60</ttl>
				<item>
			<title>New project: ZNC IRC bouncer log viewer</title>
			<link>http://blog.robin.smidsrod.no/2012/02/05/new-project-znc-irc-bouncer-log-viewer</link>
			<pubDate>Sun, 05 Feb 2012 16:54:00 +0000</pubDate>			<dc:creator>Robin Smidsr&#248;d</dc:creator>
			<category domain="main">Perl</category>
<category domain="alt">Software Development</category>			<guid isPermaLink="false">107@http://blog.robin.smidsrod.no/</guid>
						<description>&lt;p&gt;I&#039;ve just published a new project on GitHub, named &lt;a href=&quot;https://github.com/robinsmidsrod/znc-log-viewer&quot;&gt;znc-log-viewer&lt;/a&gt;, that might be useful for IRC junkies.&lt;/p&gt;

&lt;p&gt;If you use the &lt;a href=&quot;http://znc.in/&quot;&gt;ZNC IRC bouncer&lt;/a&gt; to keep you logged on to IRC all the time, you might&#039;ve enabled the &lt;a href=&quot;http://wiki.znc.in/Log&quot;&gt;log module&lt;/a&gt; to avoid missing messages when you&#039;re not connected to the bouncer.&lt;/p&gt;

&lt;p&gt;Searching those log files have always been a hassle, because they are located deep inside the .znc directory, and there are a lot of files stored in one big directory, which makes it hard to get an overview when you want to pull out your trusty friend, grep.&lt;/p&gt;

&lt;p&gt;If you actually bothered to use grep, you would also notice that a single log file has a lot of server messages in it, that is generally not interesting when you&#039;re trying to figure out what has happened since the last time you were online. In my case, I just want access to the conversations that have been going on.&lt;/p&gt;

&lt;p&gt;So if this problem is something you can relate to, feel free try out my new tool. If you find any problems with it, please open up an issue on the GitHub page.&lt;/p&gt;&lt;div class=&quot;item_footer&quot;&gt;&lt;p&gt;&lt;small&gt;&lt;a href=&quot;http://blog.robin.smidsrod.no/2012/02/05/new-project-znc-irc-bouncer-log-viewer&quot;&gt;Original post&lt;/a&gt; blogged on &lt;a href=&quot;http://b2evolution.net/&quot;&gt;b2evolution&lt;/a&gt;.&lt;/small&gt;&lt;/p&gt;&lt;/div&gt;</description>
			<content:encoded><![CDATA[<p>I've just published a new project on GitHub, named <a href="https://github.com/robinsmidsrod/znc-log-viewer">znc-log-viewer</a>, that might be useful for IRC junkies.</p>

<p>If you use the <a href="http://znc.in/">ZNC IRC bouncer</a> to keep you logged on to IRC all the time, you might've enabled the <a href="http://wiki.znc.in/Log">log module</a> to avoid missing messages when you're not connected to the bouncer.</p>

<p>Searching those log files have always been a hassle, because they are located deep inside the .znc directory, and there are a lot of files stored in one big directory, which makes it hard to get an overview when you want to pull out your trusty friend, grep.</p>

<p>If you actually bothered to use grep, you would also notice that a single log file has a lot of server messages in it, that is generally not interesting when you're trying to figure out what has happened since the last time you were online. In my case, I just want access to the conversations that have been going on.</p>

<p>So if this problem is something you can relate to, feel free try out my new tool. If you find any problems with it, please open up an issue on the GitHub page.</p><div class="item_footer"><p><small><a href="http://blog.robin.smidsrod.no/2012/02/05/new-project-znc-irc-bouncer-log-viewer">Original post</a> blogged on <a href="http://b2evolution.net/">b2evolution</a>.</small></p></div>]]></content:encoded>
								<comments>http://blog.robin.smidsrod.no/2012/02/05/new-project-znc-irc-bouncer-log-viewer#comments</comments>
			<wfw:commentRss>http://blog.robin.smidsrod.no/?tempskin=_rss2&#38;disp=comments&#38;p=107</wfw:commentRss>
		</item>
				<item>
			<title>Solved: ASUS P8Z68-V PRO GEN3 mainboard resetting instead of booting first device</title>
			<link>http://blog.robin.smidsrod.no/2011/12/22/solved-asus-p8z68-v-pro-gen3-resetting-instead-of-booting</link>
			<pubDate>Thu, 22 Dec 2011 13:13:00 +0000</pubDate>			<dc:creator>Robin Smidsr&#248;d</dc:creator>
			<category domain="main">Hardware</category>			<guid isPermaLink="false">106@http://blog.robin.smidsrod.no/</guid>
						<description>&lt;p&gt;I recently upgraded my main computer to a new &lt;a href=&quot;http://www.asus.com/Motherboards/Intel_Socket_1155/P8Z68V_PROGEN3/&quot;&gt;shiny Intel Z68-based motherboard&lt;/a&gt; with a 2nd generation Core i7. A really nice piece of hardware, I&#039;d say.&lt;/p&gt;

&lt;p&gt;But while I was setting it up I had some very odd problem. When I plugged everything in and wanted to boot it, it just didn&#039;t want to boot any devices (but the EFI BIOS loaded without problems), it reset instead.&lt;/p&gt;

&lt;p&gt;At first I thought I had a power problem, so I went out and bought a bigger PSU. But it still reset instead of booting. Then I called a friend and he thought the SATA controller might be busted. So he suggested unplugging everything and trying one part at the time.&lt;/p&gt;

&lt;p&gt;After doing a lot of cross-testing I figured out that it was the USB cable to my &lt;a href=&quot;http://www.apc.com/products/resource/include/techspec_index.cfm?base_sku=SUA1000I&amp;amp;total_watts=50&quot;&gt;APC Smart-UPS 1000&lt;/a&gt; that stopped the boot from processing. So obviously either my UPS or motherboard firmware is incompatible with each other. Luckily I was not dependent on having the UPS USB cable connected, so I just disconnected it to avoid the problem. With that component disconnected everything worked as expected.&lt;/p&gt;

&lt;p&gt;Moral of the story: Test with one component at the time if you&#039;re experiencing difficulties.&lt;/p&gt;&lt;div class=&quot;item_footer&quot;&gt;&lt;p&gt;&lt;small&gt;&lt;a href=&quot;http://blog.robin.smidsrod.no/2011/12/22/solved-asus-p8z68-v-pro-gen3-resetting-instead-of-booting&quot;&gt;Original post&lt;/a&gt; blogged on &lt;a href=&quot;http://b2evolution.net/&quot;&gt;b2evolution&lt;/a&gt;.&lt;/small&gt;&lt;/p&gt;&lt;/div&gt;</description>
			<content:encoded><![CDATA[<p>I recently upgraded my main computer to a new <a href="http://www.asus.com/Motherboards/Intel_Socket_1155/P8Z68V_PROGEN3/">shiny Intel Z68-based motherboard</a> with a 2nd generation Core i7. A really nice piece of hardware, I'd say.</p>

<p>But while I was setting it up I had some very odd problem. When I plugged everything in and wanted to boot it, it just didn't want to boot any devices (but the EFI BIOS loaded without problems), it reset instead.</p>

<p>At first I thought I had a power problem, so I went out and bought a bigger PSU. But it still reset instead of booting. Then I called a friend and he thought the SATA controller might be busted. So he suggested unplugging everything and trying one part at the time.</p>

<p>After doing a lot of cross-testing I figured out that it was the USB cable to my <a href="http://www.apc.com/products/resource/include/techspec_index.cfm?base_sku=SUA1000I&amp;total_watts=50">APC Smart-UPS 1000</a> that stopped the boot from processing. So obviously either my UPS or motherboard firmware is incompatible with each other. Luckily I was not dependent on having the UPS USB cable connected, so I just disconnected it to avoid the problem. With that component disconnected everything worked as expected.</p>

<p>Moral of the story: Test with one component at the time if you're experiencing difficulties.</p><div class="item_footer"><p><small><a href="http://blog.robin.smidsrod.no/2011/12/22/solved-asus-p8z68-v-pro-gen3-resetting-instead-of-booting">Original post</a> blogged on <a href="http://b2evolution.net/">b2evolution</a>.</small></p></div>]]></content:encoded>
								<comments>http://blog.robin.smidsrod.no/2011/12/22/solved-asus-p8z68-v-pro-gen3-resetting-instead-of-booting#comments</comments>
			<wfw:commentRss>http://blog.robin.smidsrod.no/?tempskin=_rss2&#38;disp=comments&#38;p=106</wfw:commentRss>
		</item>
				<item>
			<title>How to fix "No access" error when changing Windows file permissions</title>
			<link>http://blog.robin.smidsrod.no/2011/12/10/how-to-fix-no-access-error-when-changing-windows-file-permissions</link>
			<pubDate>Sat, 10 Dec 2011 10:51:00 +0000</pubDate>			<dc:creator>Robin Smidsr&#248;d</dc:creator>
			<category domain="main">Software</category>
<category domain="alt">Hardware</category>			<guid isPermaLink="false">105@http://blog.robin.smidsrod.no/</guid>
						<description>&lt;p&gt;Yesterday I helped my father to install his new computer with Windows 7. For several reasons I shall not mention, it was in the best interest to take the hard drive of the old (broken) computer and copy all the data from it to the new. What he (and me) wanted was to keep all the settings and other stuff and just reinstall his programs. That meant copying the entire AppData folder under his user account in addition to his normal files.&lt;/p&gt;

&lt;p&gt;During this process obviously all files had kept the old SIDs, and as they weren&#039;t matching his new SID (on the new computer) I was having problems accessing the files. Lots of stuff didn&#039;t work. I was getting &quot;No access&quot; messages left and right when I tried to change the permissions of those copied files. That is when it dawned on me that even though his user account was registered as an administrator, it wasn&#039;t able to change the permission bits on those files.&lt;/p&gt;

&lt;p&gt;To avoid having to mess around forever to reset permissions to default inherited ACL on all those files (around 30000 or so of them) I dug a bit around on the Internet and found out that there is a command in Windows 7 called icacls.exe that works very much in the same way as chmod on Linux, just that it supports NTFS ACLs.&lt;/p&gt;

&lt;p&gt;After reading around I found a forum post that mentioned it, and was redirected to &lt;a href=&quot;http://technet.microsoft.com/en-us/library/cc753525%28WS.10%29.aspx&quot;&gt;Microsoft&#039;s knowledge base article on icacls.exe&lt;/a&gt;. After looking over that one I found this command line that solved everything.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;cd C:\Users\username
icacls * /reset /T
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The &lt;code&gt;/T&lt;/code&gt; switch makes it run recursively. &lt;code&gt;/reset&lt;/code&gt;, as the KB article explains, replaces the ACL on each file and folder with the default inherited ACL that just uses the ACL on the parent folder.&lt;/p&gt;

&lt;p&gt;The last thing that was required was to run this command inside a cmd.exe shell with elevated privileges. You can do that by finding the Command Prompt icon in your start menu and right-clicking &quot;Run as administrator&quot;.&lt;/p&gt;

&lt;p&gt;Be aware that mucking around with ACLs, if you don&#039;t know what you&#039;re doing, can seriously break your system in more ways than you can think of. It can also open you up to various attacks because of more relaxed permissions.&lt;/p&gt;

&lt;p&gt;If you like what you just read, please subscribe to the feed or &lt;a href=&quot;http://twitter.com/robinsmidsrod&quot;&gt;follow me on Twitter&lt;/a&gt; or any of the other networks I&#039;m registered in to be updated when I post something new.&lt;/p&gt;&lt;div class=&quot;item_footer&quot;&gt;&lt;p&gt;&lt;small&gt;&lt;a href=&quot;http://blog.robin.smidsrod.no/2011/12/10/how-to-fix-no-access-error-when-changing-windows-file-permissions&quot;&gt;Original post&lt;/a&gt; blogged on &lt;a href=&quot;http://b2evolution.net/&quot;&gt;b2evolution&lt;/a&gt;.&lt;/small&gt;&lt;/p&gt;&lt;/div&gt;</description>
			<content:encoded><![CDATA[<p>Yesterday I helped my father to install his new computer with Windows 7. For several reasons I shall not mention, it was in the best interest to take the hard drive of the old (broken) computer and copy all the data from it to the new. What he (and me) wanted was to keep all the settings and other stuff and just reinstall his programs. That meant copying the entire AppData folder under his user account in addition to his normal files.</p>

<p>During this process obviously all files had kept the old SIDs, and as they weren't matching his new SID (on the new computer) I was having problems accessing the files. Lots of stuff didn't work. I was getting "No access" messages left and right when I tried to change the permissions of those copied files. That is when it dawned on me that even though his user account was registered as an administrator, it wasn't able to change the permission bits on those files.</p>

<p>To avoid having to mess around forever to reset permissions to default inherited ACL on all those files (around 30000 or so of them) I dug a bit around on the Internet and found out that there is a command in Windows 7 called icacls.exe that works very much in the same way as chmod on Linux, just that it supports NTFS ACLs.</p>

<p>After reading around I found a forum post that mentioned it, and was redirected to <a href="http://technet.microsoft.com/en-us/library/cc753525%28WS.10%29.aspx">Microsoft's knowledge base article on icacls.exe</a>. After looking over that one I found this command line that solved everything.</p>

<pre><code>cd C:\Users\username
icacls * /reset /T
</code></pre>

<p>The <code>/T</code> switch makes it run recursively. <code>/reset</code>, as the KB article explains, replaces the ACL on each file and folder with the default inherited ACL that just uses the ACL on the parent folder.</p>

<p>The last thing that was required was to run this command inside a cmd.exe shell with elevated privileges. You can do that by finding the Command Prompt icon in your start menu and right-clicking "Run as administrator".</p>

<p>Be aware that mucking around with ACLs, if you don't know what you're doing, can seriously break your system in more ways than you can think of. It can also open you up to various attacks because of more relaxed permissions.</p>

<p>If you like what you just read, please subscribe to the feed or <a href="http://twitter.com/robinsmidsrod">follow me on Twitter</a> or any of the other networks I'm registered in to be updated when I post something new.</p><div class="item_footer"><p><small><a href="http://blog.robin.smidsrod.no/2011/12/10/how-to-fix-no-access-error-when-changing-windows-file-permissions">Original post</a> blogged on <a href="http://b2evolution.net/">b2evolution</a>.</small></p></div>]]></content:encoded>
								<comments>http://blog.robin.smidsrod.no/2011/12/10/how-to-fix-no-access-error-when-changing-windows-file-permissions#comments</comments>
			<wfw:commentRss>http://blog.robin.smidsrod.no/?tempskin=_rss2&#38;disp=comments&#38;p=105</wfw:commentRss>
		</item>
				<item>
			<title>Config::Role - Object constructor parameters from file made easy</title>
			<link>http://blog.robin.smidsrod.no/2011/10/18/config-role-object-constructor-parameters-from-file-made-easy</link>
			<pubDate>Tue, 18 Oct 2011 19:05:00 +0000</pubDate>			<dc:creator>Robin Smidsr&#248;d</dc:creator>
			<category domain="main">Perl</category>
<category domain="alt">Software Development</category>			<guid isPermaLink="false">104@http://blog.robin.smidsrod.no/</guid>
						<description>&lt;p&gt;After I wrote that big 5-part article on &lt;a href=&quot;/2011/09/30/implementing-www-lastfm-part-1&quot;&gt;WWW::LastFM / XML::Rabbit&lt;/a&gt; I noticed several things that could be improved. One of them was the fact that loading configuration information from a file in the home directory was a pretty generic thing to do. I decided to factor that code out and release it as a separate CPAN module that can be used by any Moose class.&lt;/p&gt;

&lt;p&gt;If you look at the code used to read the configuration information in the article mentioned above you&#039;ll see that it has improved quite a bit. The code below is shorter, but does the same thing as the old code.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;xmp&gt;package WWW::LastFM;
use Moose;
use namespace::autoclean;

# Read configuration from ~/.lastfm.ini,
# available in $self-&gt;config
has &#039;config_filename&#039; =&gt; (
    is         =&gt; &#039;ro&#039;,
    isa        =&gt; &#039;Str&#039;,
    lazy_build =&gt; 1,
);
sub _build_config_filename { &#039;.lastfm.ini&#039; }
with &#039;Config::Role&#039;;

# And here we have our api key and secret
has &#039;api_key&#039; =&gt; (
    is         =&gt; &#039;ro&#039;,
    isa        =&gt; &#039;Str&#039;,
    lazy_build =&gt; 1,
);
sub _build_api_key {
    return (shift)-&gt;config-&gt;{&#039;API&#039;}-&gt;{&#039;key&#039;};
}

has &#039;api_secret&#039; =&gt; (
    is         =&gt; &#039;ro&#039;,
    isa        =&gt; &#039;Str&#039;,
    lazy_build =&gt; 1,
);
sub _build_api_secret {
    return (shift)-&gt;config-&gt;{&#039;API&#039;}-&gt;{&#039;secret&#039;};
}
...
&lt;/xmp&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The reason this approach is quite nice, is because it allows you to load e.g. username and password from a configuration file with the ability to override it in the constructor. You can also quite easily override the location to search for the configuration file in your tests by mocking the location returned by &lt;code&gt;File::HomeDir-&amp;gt;my_data()&lt;/code&gt;. Alternatively, you can specify either the &lt;code&gt;config_file&lt;/code&gt; or &lt;code&gt;config_files&lt;/code&gt; constructor parameters to explicitly specify the path to your configuration file (or an array reference of them).

&lt;p&gt;I usually start my test case with code like this to override where to look for the configuration file for the test environment. Then I just put a copy of my configuration file inside the &lt;code&gt;t&lt;/code&gt; folder with the same file name as usual.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&lt;xmp&gt;# Mock home/config dir location
{
    use File::HomeDir;
    no warnings &#039;redefine&#039;;
    sub File::HomeDir::my_data { return &#039;t&#039;; }
}
&lt;/xmp&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The configuration file is loaded via &lt;code&gt;&lt;a href=&quot;https://metacpan.org/module/Config::Any&quot;&gt;Config::Any&lt;/a&gt;-&amp;gt;load_files()&lt;/code&gt;, so you can use any format supported by that module. Your config attribute will always be represented as a hash reference.&lt;/p&gt;

&lt;p&gt;If you find this article or the CPAN module &lt;a href=&quot;https://metacpan.org/module/Config::Role&quot;&gt;Config::Role&lt;/a&gt; useful I&#039;d be very happy if you share it with your friends on Facebook, Google+, Twitter or other service you use. If you like what you see, follow me on any of the networks at the top of the page of &lt;a href=&quot;http://blog.robin.smidsrod.no&quot;&gt;my blog&lt;/a&gt; and subscribe to my feed to keep informed on when I post something new.&lt;/p&gt;&lt;/p&gt;&lt;div class=&quot;item_footer&quot;&gt;&lt;p&gt;&lt;small&gt;&lt;a href=&quot;http://blog.robin.smidsrod.no/2011/10/18/config-role-object-constructor-parameters-from-file-made-easy&quot;&gt;Original post&lt;/a&gt; blogged on &lt;a href=&quot;http://b2evolution.net/&quot;&gt;b2evolution&lt;/a&gt;.&lt;/small&gt;&lt;/p&gt;&lt;/div&gt;</description>
			<content:encoded><![CDATA[<p>After I wrote that big 5-part article on <a href="http://blog.robin.smidsrod.no/2011/09/30/implementing-www-lastfm-part-1">WWW::LastFM / XML::Rabbit</a> I noticed several things that could be improved. One of them was the fact that loading configuration information from a file in the home directory was a pretty generic thing to do. I decided to factor that code out and release it as a separate CPAN module that can be used by any Moose class.</p>

<p>If you look at the code used to read the configuration information in the article mentioned above you'll see that it has improved quite a bit. The code below is shorter, but does the same thing as the old code.</p>

<pre><code><xmp>package WWW::LastFM;
use Moose;
use namespace::autoclean;

# Read configuration from ~/.lastfm.ini,
# available in $self->config
has 'config_filename' => (
    is         => 'ro',
    isa        => 'Str',
    lazy_build => 1,
);
sub _build_config_filename { '.lastfm.ini' }
with 'Config::Role';

# And here we have our api key and secret
has 'api_key' => (
    is         => 'ro',
    isa        => 'Str',
    lazy_build => 1,
);
sub _build_api_key {
    return (shift)->config->{'API'}->{'key'};
}

has 'api_secret' => (
    is         => 'ro',
    isa        => 'Str',
    lazy_build => 1,
);
sub _build_api_secret {
    return (shift)->config->{'API'}->{'secret'};
}
...
</xmp></code></pre>

<p>The reason this approach is quite nice, is because it allows you to load e.g. username and password from a configuration file with the ability to override it in the constructor. You can also quite easily override the location to search for the configuration file in your tests by mocking the location returned by <code>File::HomeDir-&gt;my_data()</code>. Alternatively, you can specify either the <code>config_file</code> or <code>config_files</code> constructor parameters to explicitly specify the path to your configuration file (or an array reference of them).

<p>I usually start my test case with code like this to override where to look for the configuration file for the test environment. Then I just put a copy of my configuration file inside the <code>t</code> folder with the same file name as usual.</p>

<pre><code><xmp># Mock home/config dir location
{
    use File::HomeDir;
    no warnings 'redefine';
    sub File::HomeDir::my_data { return 't'; }
}
</xmp></code></pre>

<p>The configuration file is loaded via <code><a href="https://metacpan.org/module/Config::Any">Config::Any</a>-&gt;load_files()</code>, so you can use any format supported by that module. Your config attribute will always be represented as a hash reference.</p>

<p>If you find this article or the CPAN module <a href="https://metacpan.org/module/Config::Role">Config::Role</a> useful I'd be very happy if you share it with your friends on Facebook, Google+, Twitter or other service you use. If you like what you see, follow me on any of the networks at the top of the page of <a href="http://blog.robin.smidsrod.no">my blog</a> and subscribe to my feed to keep informed on when I post something new.</p></p><div class="item_footer"><p><small><a href="http://blog.robin.smidsrod.no/2011/10/18/config-role-object-constructor-parameters-from-file-made-easy">Original post</a> blogged on <a href="http://b2evolution.net/">b2evolution</a>.</small></p></div>]]></content:encoded>
								<comments>http://blog.robin.smidsrod.no/2011/10/18/config-role-object-constructor-parameters-from-file-made-easy#comments</comments>
			<wfw:commentRss>http://blog.robin.smidsrod.no/?tempskin=_rss2&#38;disp=comments&#38;p=104</wfw:commentRss>
		</item>
				<item>
			<title>How to automatically block IPs that do a dictionary attack on your SSH server</title>
			<link>http://blog.robin.smidsrod.no/2011/10/07/autoblock-sshd-dictionary-attacks</link>
			<pubDate>Fri, 07 Oct 2011 18:54:00 +0000</pubDate>			<dc:creator>Robin Smidsr&#248;d</dc:creator>
			<category domain="main">Software</category>
<category domain="alt">Perl</category>			<guid isPermaLink="false">103@http://blog.robin.smidsrod.no/</guid>
						<description>&lt;p&gt;Have you ever noticed that the sshd on your publicly facing machines gets bombarded with dictionary attacks several times per day? This problem is mostly an annoyance, as it fills up the logs with lots of &lt;em&gt;User authentication failed, wrong password for &amp;lt;username&amp;gt;&lt;/em&gt; messages. There are of course several ways to work around this problem, and the most common one is to run sshd on another port than 22. I find that approach cumbersome, because it means you&#039;ll always have to configure your client software to connect to a non-standard port, and in lots of cases a firewall at your location might be blocking the traffic as well. &lt;strong&gt;Isn&#039;t there a way to block these bothersome users instead?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I recently read an article in &lt;a href=&quot;http://lj.mybigcommerce.com/products/Linux-Journal-October-2011%2C-%23210-%28PDF-and-EPUB%29.html&quot;&gt;Linux Journal #210&lt;/a&gt; that talked about a new feature in the Linux kernel called &lt;a href=&quot;http://ipset.netfilter.org/&quot;&gt;ipset&lt;/a&gt;. It allows you to create sets that store IP addresses which can dynamically be added to and removed from. This sets can be configured as selectors in iptables rules, so that you can perform actions on any IP address in the set, like dropping the packets. You can also create iptables rules that add or remove an IP from a set.&lt;/p&gt;

&lt;p&gt;In Debian it is straight-forward to install ipset. Type in these commands to install ipset and the required kernel module. It should ensure it is automatically recompiled whenever you do a kernel upgrade.&lt;/p&gt;

&lt;code&gt;&lt;pre&gt;
$ apt-get install ipset xtables-addons-source
$ module-assistant auto-install xtables-addons
&lt;/pre&gt;&lt;/code&gt;

&lt;p&gt;I asked around on IRC if it was possible to make sshd execute a shell script once the amount of logins from an IP went above some configurable threshold, but apparently it is not possible. Someone pointed me at &lt;a href=&quot;http://www.fail2ban.org/&quot;&gt;fail2ban&lt;/a&gt; which is a system that scans your syslog looking for failed logins and turn them into blocking iptables rules (and probably more, I didn&#039;t look very closely). I thought that this was too slow, as I wanted something that triggered as soon as multiple failed logins for a user reached a certain threshold, but I wanted the block to be temporary, not permanent.&lt;/p&gt;

&lt;p&gt;That is when I remembered that syslog (rsyslog in my case) can be configured to run the log messages through a program. I put together the program below that reads incoming messages from rsyslog with auth.info facility/level (default for sshd on Debian) and does the necessary things to ensure the offending IP is added to my autoblock ipset if it triggers a certain amount of failed logins in a short period. The autoblock ipset uses the iptree storage module that has a --timeout parameter to automatically purge entries from the set after a given time. I set it to 3600 seconds (1 hour).&lt;/p&gt;

&lt;p&gt;What is also quite cool, is that I have a rule that will immediately put the IP of anyone that tries to connect to my SMTP port (I don&#039;t run a mail server on my firewall) on the autoblock list. This particular bit happens completely inside netfilter, so its effect is immediate. Bye bye spammers! Try to connect to my (non-existent) email server and you&#039;re instantly blocked for an hour. And you didn&#039;t even know what hit you. :)&lt;/p&gt;

&lt;p&gt;The reason I like the temporary block is that sometimes I port-scan my own server or do other strange things with it from remote computers, and the fact that I know the block will be lifted after an hour means I can continue without having to get physical access to server to remove the block. I can just smack myself in the forehead and wait an hour and continue whatever stupid thing I was doing.&lt;/p&gt;

&lt;p&gt;&lt;i&gt;PS: Remember that lots of IRC servers like to port-scan your IP when you connect to them, so you might need to put up some exceptions for those if you&#039;re an active IRC user and your particular IRC server likes to probe the SMTP port.&lt;/i&gt;&lt;/p&gt;

&lt;script src=&quot;https://gist.github.com/1270550.js?file=sshd_autoblock&quot;&gt;&lt;/script&gt;
&lt;noscript&gt;&lt;a href=&quot;https://gist.github.com/1270550&quot;&gt;Temporarily block IPs that try to brute force attack sshd automatically with ipset and rsyslog&lt;a&gt;&lt;/a&gt;&lt;/a&gt;&lt;/noscript&gt;&lt;div class=&quot;item_footer&quot;&gt;&lt;p&gt;&lt;small&gt;&lt;a href=&quot;http://blog.robin.smidsrod.no/2011/10/07/autoblock-sshd-dictionary-attacks&quot;&gt;Original post&lt;/a&gt; blogged on &lt;a href=&quot;http://b2evolution.net/&quot;&gt;b2evolution&lt;/a&gt;.&lt;/small&gt;&lt;/p&gt;&lt;/div&gt;</description>
			<content:encoded><![CDATA[<p>Have you ever noticed that the sshd on your publicly facing machines gets bombarded with dictionary attacks several times per day? This problem is mostly an annoyance, as it fills up the logs with lots of <em>User authentication failed, wrong password for &lt;username&gt;</em> messages. There are of course several ways to work around this problem, and the most common one is to run sshd on another port than 22. I find that approach cumbersome, because it means you'll always have to configure your client software to connect to a non-standard port, and in lots of cases a firewall at your location might be blocking the traffic as well. <strong>Isn't there a way to block these bothersome users instead?</strong></p>

<p>I recently read an article in <a href="http://lj.mybigcommerce.com/products/Linux-Journal-October-2011%2C-%23210-%28PDF-and-EPUB%29.html">Linux Journal #210</a> that talked about a new feature in the Linux kernel called <a href="http://ipset.netfilter.org/">ipset</a>. It allows you to create sets that store IP addresses which can dynamically be added to and removed from. This sets can be configured as selectors in iptables rules, so that you can perform actions on any IP address in the set, like dropping the packets. You can also create iptables rules that add or remove an IP from a set.</p>

<p>In Debian it is straight-forward to install ipset. Type in these commands to install ipset and the required kernel module. It should ensure it is automatically recompiled whenever you do a kernel upgrade.</p>

<code><pre>
$ apt-get install ipset xtables-addons-source
$ module-assistant auto-install xtables-addons
</pre></code>

<p>I asked around on IRC if it was possible to make sshd execute a shell script once the amount of logins from an IP went above some configurable threshold, but apparently it is not possible. Someone pointed me at <a href="http://www.fail2ban.org/">fail2ban</a> which is a system that scans your syslog looking for failed logins and turn them into blocking iptables rules (and probably more, I didn't look very closely). I thought that this was too slow, as I wanted something that triggered as soon as multiple failed logins for a user reached a certain threshold, but I wanted the block to be temporary, not permanent.</p>

<p>That is when I remembered that syslog (rsyslog in my case) can be configured to run the log messages through a program. I put together the program below that reads incoming messages from rsyslog with auth.info facility/level (default for sshd on Debian) and does the necessary things to ensure the offending IP is added to my autoblock ipset if it triggers a certain amount of failed logins in a short period. The autoblock ipset uses the iptree storage module that has a --timeout parameter to automatically purge entries from the set after a given time. I set it to 3600 seconds (1 hour).</p>

<p>What is also quite cool, is that I have a rule that will immediately put the IP of anyone that tries to connect to my SMTP port (I don't run a mail server on my firewall) on the autoblock list. This particular bit happens completely inside netfilter, so its effect is immediate. Bye bye spammers! Try to connect to my (non-existent) email server and you're instantly blocked for an hour. And you didn't even know what hit you. :)</p>

<p>The reason I like the temporary block is that sometimes I port-scan my own server or do other strange things with it from remote computers, and the fact that I know the block will be lifted after an hour means I can continue without having to get physical access to server to remove the block. I can just smack myself in the forehead and wait an hour and continue whatever stupid thing I was doing.</p>

<p><i>PS: Remember that lots of IRC servers like to port-scan your IP when you connect to them, so you might need to put up some exceptions for those if you're an active IRC user and your particular IRC server likes to probe the SMTP port.</i></p>

<script src="https://gist.github.com/1270550.js?file=sshd_autoblock"></script>
<noscript><a href="https://gist.github.com/1270550">Temporarily block IPs that try to brute force attack sshd automatically with ipset and rsyslog<a></a></a></noscript><div class="item_footer"><p><small><a href="http://blog.robin.smidsrod.no/2011/10/07/autoblock-sshd-dictionary-attacks">Original post</a> blogged on <a href="http://b2evolution.net/">b2evolution</a>.</small></p></div>]]></content:encoded>
								<comments>http://blog.robin.smidsrod.no/2011/10/07/autoblock-sshd-dictionary-attacks#comments</comments>
			<wfw:commentRss>http://blog.robin.smidsrod.no/?tempskin=_rss2&#38;disp=comments&#38;p=103</wfw:commentRss>
		</item>
				<item>
			<title>Implementing WWW::LastFM with XML::Rabbit - Part 5</title>
			<link>http://blog.robin.smidsrod.no/2011/10/04/implementing-www-lastfm-part-5</link>
			<pubDate>Tue, 04 Oct 2011 11:37:00 +0000</pubDate>			<dc:creator>Robin Smidsr&#248;d</dc:creator>
			<category domain="main">Perl</category>
<category domain="alt">Software Development</category>			<guid isPermaLink="false">102@http://blog.robin.smidsrod.no/</guid>
						<description>&lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;/2011/09/30/implementing-www-lastfm-part-1&quot;&gt;Part 1 of 5&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;/2011/10/01/implementing-www-lastfm-part-2&quot;&gt;Part 2 of 5&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;/2011/10/02/implementing-www-lastfm-part-3&quot;&gt;Part 3 of 5&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;/2011/10/03/implementing-www-lastfm-part-4&quot;&gt;Part 4 of 5&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;strong&gt;Part 5 of 5&lt;/strong&gt;&lt;/li&gt;
    &lt;/ul&gt;
    &lt;p&gt;
      In the previous article we started implementing the &lt;code&gt;geo.getEvents&lt;/code&gt; Last.FM API call. In this article we will complete the implementation of the &lt;code&gt;Event&lt;/code&gt; class and create a small application to display events for a specific location in a compact format.
    &lt;/p&gt;
    &lt;h3&gt;
      Implementing the &lt;code&gt;&amp;lt;event/&amp;gt;&lt;/code&gt; XML chunk extractor
    &lt;/h3&gt;
    &lt;p&gt;
      So let&#039;s move on to the WWW::LastFM::Response::Event class. This is one of the core classes for our particular problem domain. Everything up to here has just been stuff needed to build an extensible framework for sending our queries to the Last.FM API and extract values from the response data. Let&#039;s get on with it. Create &lt;code&gt;lib/WWW/LastFM/Response/Event.pm&lt;/code&gt; with this content:
    &lt;/p&gt;
    &lt;pre&gt;
&lt;code&gt;package WWW::LastFM::Response::Event;
use XML::Rabbit;

use HTML::FormatText;
use DateTime::Format::HTTP;

# Last.FM-specific data
has_xpath_value &#039;id&#039;  =&amp;gt; &#039;./id&#039;;
has_xpath_value &#039;tag&#039; =&amp;gt; &#039;./tag&#039;;
has_xpath_value &#039;url&#039; =&amp;gt; &#039;./url&#039;;

# The actual event-related data
has_xpath_value &#039;title&#039;            =&amp;gt; &#039;./title&#039;;
has_xpath_value &#039;description_html&#039; =&amp;gt; &#039;./description&#039;;
has_xpath_value &#039;website&#039;          =&amp;gt; &#039;./website&#039;;
has_xpath_value &#039;start_date&#039;       =&amp;gt; &#039;./startDate&#039;;
has_xpath_value &#039;headliner&#039;        =&amp;gt; &#039;./artists/headliner&#039;;
has_xpath_value_list &#039;_artists&#039;    =&amp;gt; &#039;./artists/artist&#039;,
    handles =&amp;gt; {
        &#039;artists&#039;      =&amp;gt; &#039;elements&#039;,
    }
;
has_xpath_object &#039;venue&#039;     =&amp;gt; &#039;./venue&#039;    =&amp;gt; &#039;WWW::LastFM::Response::Venue&#039;;
has_xpath_value_list &#039;_tags&#039; =&amp;gt; &#039;./tags/tag&#039;,
    handles =&amp;gt; {
        &#039;tags&#039; =&amp;gt; &#039;elements&#039;,
    },
;

# Some various attendance-related data
has_xpath_value &#039;ticket_count&#039; =&amp;gt; &#039;./tickets&#039;;
has_xpath_value &#039;attendance&#039;   =&amp;gt; &#039;./attendance&#039;;
has_xpath_value &#039;cancelled&#039;    =&amp;gt; &#039;./cancelled&#039;;
has_xpath_value &#039;review_count&#039; =&amp;gt; &#039;./reviews&#039;;

# Event image URLs
has_xpath_value &#039;image_s&#039;  =&amp;gt; &#039;./image[@size=&quot;small&quot;]&#039;;
has_xpath_value &#039;image_m&#039;  =&amp;gt; &#039;./image[@size=&quot;medium&quot;]&#039;;
has_xpath_value &#039;image_l&#039;  =&amp;gt; &#039;./image[@size=&quot;large&quot;]&#039;;
has_xpath_value &#039;image_xl&#039; =&amp;gt; &#039;./image[@size=&quot;extralarge&quot;]&#039;;

# The description value, converted to plain text
has &#039;description&#039; =&amp;gt; (
    is         =&amp;gt; &#039;ro&#039;,
    isa        =&amp;gt; &#039;Str&#039;,
    lazy_build =&amp;gt; 1,
);

sub _build_description {
    my ($self) = @_;
    return HTML::FormatText-&amp;gt;format_string(
        $self-&amp;gt;description_html,
        leftmargin  =&amp;gt;  0,
        rightmargin =&amp;gt; 79,
    );
}

# Convert the start_date to a DateTime class which is much more useful
has &#039;date&#039; =&amp;gt; (
    is         =&amp;gt; &#039;ro&#039;,
    isa        =&amp;gt; &#039;DateTime&#039;,
    lazy_build =&amp;gt; 1,
);

sub _build_date {
    my ($self) = @_;
    return DateTime::Format::HTTP-&amp;gt;parse_datetime(
        $self-&amp;gt;start_date
    );
}

finalize_class();
&lt;/code&gt;
&lt;/pre&gt;
    &lt;p&gt;
      There shouldn&#039;t be anything surprising in this piece of code. The only thing you haven&#039;t seen before is the use of XPath attribute values as selectors. We&#039;ve already covered all the various declarations, so let&#039;s just continue with WWW::LastFM::Response::Venue, so that we can have a look at how to deal with the namespaced latitude/longtitude information. Create &lt;code&gt;lib/WWW/LastFM/Response/Venue.pm&lt;/code&gt; with this content:
    &lt;/p&gt;
    &lt;pre&gt;
&lt;code&gt;package WWW::LastFM::Response::Venue;
use XML::Rabbit;

# Last.FM-specific data
has_xpath_value &#039;id&#039;  =&amp;gt; &#039;./id&#039;;
has_xpath_value &#039;url&#039; =&amp;gt; &#039;./url&#039;;

# The actual venue-related data
has_xpath_value &#039;name&#039;         =&amp;gt; &#039;./name&#039;;
has_xpath_value &#039;website&#039;      =&amp;gt; &#039;./website&#039;;
has_xpath_value &#039;phone_number&#039; =&amp;gt; &#039;./phonenumber&#039;;

# Location-related data
has_xpath_value &#039;city&#039;        =&amp;gt; &#039;./location/city&#039;;
has_xpath_value &#039;country&#039;     =&amp;gt; &#039;./location/country&#039;;
has_xpath_value &#039;street&#039;      =&amp;gt; &#039;./location/street&#039;;
has_xpath_value &#039;postal_code&#039; =&amp;gt; &#039;./location/postalcode&#039;;
has_xpath_value &#039;latitude&#039;    =&amp;gt; &#039;./location/geo:point/geo:lat&#039;;
has_xpath_value &#039;longitude&#039;   =&amp;gt; &#039;./location/geo:point/geo:long&#039;;

# Venue image URLs
has_xpath_value &#039;image_s&#039;  =&amp;gt; &#039;./image[@size=&quot;small&quot;]&#039;;
has_xpath_value &#039;image_m&#039;  =&amp;gt; &#039;./image[@size=&quot;medium&quot;]&#039;;
has_xpath_value &#039;image_l&#039;  =&amp;gt; &#039;./image[@size=&quot;large&quot;]&#039;;
has_xpath_value &#039;image_xl&#039; =&amp;gt; &#039;./image[@size=&quot;extralarge&quot;]&#039;;

# Convenience attribute for combined address
has &#039;address&#039; =&amp;gt; (
    is         =&amp;gt; &#039;ro&#039;,
    isa        =&amp;gt; &#039;Str&#039;,
    lazy_build =&amp;gt; 1,
);

sub _build_address {
    my ($self) = @_;
    return $self-&amp;gt;name . &#039;, &#039;
         . ( $self-&amp;gt;street ? $self-&amp;gt;street . &#039;, &#039; : &quot;&quot; )
         . ( $self-&amp;gt;postal_code ? $self-&amp;gt;postal_code . &#039; &#039; : &quot;&quot; )
         . $self-&amp;gt;city . &#039;, &#039;
         . $self-&amp;gt;country;
}

finalize_class();
&lt;/code&gt;
&lt;/pre&gt;
    &lt;p&gt;
      This is just more of the same things we&#039;ve been doing. Notice the simplicity in dealing with namespaces in the XPath queries for latitude and longitude? It&#039;s really that simple.
    &lt;/p&gt;
    &lt;h3&gt;
      lastfm_events.pl, a simple WWW::LastFM client
    &lt;/h3&gt;
    &lt;p&gt;
      Let&#039;s finish it up by making a fairly simple command line app to search for events. Create &lt;code&gt;bin/lastfm_events.pl&lt;/code&gt; with the following content:
    &lt;/p&gt;
    &lt;pre&gt;
&lt;code&gt;#!/usr/bin/env perl

use strict;
use warnings;
use rlib;
use feature qw(say);

use WWW::LastFM;
use Getopt::Long;

# PODNAME: lastfm_eventss.pl
# ABSTRACT: Search the Last.FM API for events/concerts

STDOUT-&amp;gt;binmode(&quot;:utf8&quot;);

# Read command line flags
my $limit;
my $distance;
my $help;
GetOptions(
    &quot;limit=i&quot;    =&amp;gt; \$limit,
    &quot;distance=i&quot; =&amp;gt; \$distance,
    &quot;help&quot;       =&amp;gt; \$help,
);

die &amp;lt;&amp;lt;&quot;EOM&quot; if $help;
Usage: $0 [&amp;lt;options&amp;gt;] [&amp;lt;location&amp;gt;]
Options:
    --limit &amp;lt;num&amp;gt;   ; Number of events to return
    --distance &amp;lt;km&amp;gt; ; How large area to search
EOM

my $location = shift;

# Perform query
my $event_list = WWW::LastFM-&amp;gt;new-&amp;gt;geo-&amp;gt;get_events(
    ( defined $limit    ? ( limit    =&amp;gt; $limit )    : () ),
    ( defined $distance ? ( distance =&amp;gt; $distance ) : () ),
    ( defined $location ? ( location =&amp;gt; $location ) : () ),
)-&amp;gt;events;
my @events = $event_list-&amp;gt;all;

# Format and output results
if ( @events &amp;gt; 0 ) {
    foreach my $event ( sort { $a-&amp;gt;date cmp $b-&amp;gt;date } @events ) {
        my $formatted_date = DateTime::Format::HTTP-&amp;gt;format_datetime($event-&amp;gt;date);
        my $headliner = $event-&amp;gt;title ne $event-&amp;gt;headliner ? &quot; with &quot; . $event-&amp;gt;headliner : &quot;&quot;;
        my @additional_artists = grep { ! $event-&amp;gt;headliner } $event-&amp;gt;artists;
        say $formatted_date . &quot;: &quot;
          . $event-&amp;gt;title
          . $headliner
          . ( scalar @additional_artists ? &quot; and &quot; . join(&quot;, &quot;, @additional_artists ) : &quot;&quot; )
          . &quot; @ &quot; . $event-&amp;gt;venue-&amp;gt;address;
    }
}
else {
    say STDERR &quot;No events found for location &#039;&quot; . $event_list-&amp;gt;location . &quot;&#039;&quot;;
}

1;
&lt;/code&gt;
&lt;/pre&gt;
    &lt;p&gt;
      Let&#039;s have some fun with it!
    &lt;/p&gt;
    &lt;pre&gt;
&lt;code&gt;$ bin/lastfm_events.pl --distance 50 --limit 50 T&amp;#248;nsberg
Fri, 04 Nov 2011 19:30:00 GMT: Melissa Horn @ Kulturhuset B&amp;#248;lgen, Larvik, Norway
Fri, 18 Nov 2011 19:00:00 GMT: Konsert, Bj&amp;#248;rn Eidsv&amp;#229;g med band  with Bj&amp;#248;rn Eidsv&amp;#229;g @ Parkteatret, Moss, Norway
Fri, 16 Dec 2011 19:00:00 GMT: Helene B&amp;#248;ksle p&amp;#229; Domkirkefestivalen i T&amp;#248;nsberg Domkirke with Helene B&amp;#248;ksle @ T&amp;#248;nsberg Domkirke, T&amp;#248;nsberg, Norway
Fri, 16 Dec 2011 19:31:01 GMT: Ljungblut @ Total, Stoltenbergsgata 46, 3110 T&amp;#248;nsberg, Norway
Sat, 17 Dec 2011 19:34:01 GMT: Ljungblut @ Total, Stoltenbergsgata 46, 3110 T&amp;#248;nsberg, Norway
&lt;/code&gt;
&lt;/pre&gt;
    &lt;p&gt;
      This shows the events happening around where I live. Not really much to be cheerful about. Hopefully you&#039;ll get some more interesting results for where you live.
    &lt;/p&gt;
    &lt;h3&gt;
      Conclusion
    &lt;/h3&gt;
    &lt;p&gt;
      I wrote this article mostly to showcase how easy it is extract useful information from XML documents with &lt;a href=&quot;http://metacpan.org/module/XML::Rabbit&quot;&gt;XML::Rabbit&lt;/a&gt; and augment it with additional &lt;a href=&quot;http://metacpan.org/module/Moose::Manual&quot;&gt;Moose&lt;/a&gt; magic. I don&#039;t really go to concerts very often, but I figured it was a useful example that some of you would find interesting (instead of just using some hand-crafted example XML).
    &lt;/p&gt;
    &lt;p&gt;
      If you want to see additional API calls implemented I urge you to &lt;a href=&quot;http://robin.smidsrod.no/contact&quot;&gt;get in touch&lt;/a&gt; with me and help me to improve it. I&#039;ve shown you how I&#039;ve done it this far. With some help from various &lt;a href=&quot;http://learn.perl.org&quot;&gt;Perl learning resources&lt;/a&gt; I&#039;m sure you&#039;d be able to whip up a patch with some additional features (or tests). Please &lt;a href=&quot;http://twitter.com/robinsmidsrod&quot;&gt;follow me on Twitter&lt;/a&gt; or &lt;a href=&quot;https://github.com/robinsmidsrod&quot;&gt;GitHub&lt;/a&gt; or subscribe to &lt;a href=&quot;http://blog.robin.smidsrod.no/&quot;&gt;my blog&lt;/a&gt; if you want to stay informed with what I&#039;m doing.
    &lt;/p&gt;&lt;div class=&quot;item_footer&quot;&gt;&lt;p&gt;&lt;small&gt;&lt;a href=&quot;http://blog.robin.smidsrod.no/2011/10/04/implementing-www-lastfm-part-5&quot;&gt;Original post&lt;/a&gt; blogged on &lt;a href=&quot;http://b2evolution.net/&quot;&gt;b2evolution&lt;/a&gt;.&lt;/small&gt;&lt;/p&gt;&lt;/div&gt;</description>
			<content:encoded><![CDATA[<ul>
      <li><a href="http://blog.robin.smidsrod.no/2011/09/30/implementing-www-lastfm-part-1">Part 1 of 5</a></li>
      <li><a href="http://blog.robin.smidsrod.no/2011/10/01/implementing-www-lastfm-part-2">Part 2 of 5</a></li>
      <li><a href="http://blog.robin.smidsrod.no/2011/10/02/implementing-www-lastfm-part-3">Part 3 of 5</a></li>
      <li><a href="http://blog.robin.smidsrod.no/2011/10/03/implementing-www-lastfm-part-4">Part 4 of 5</a></li>
      <li><strong>Part 5 of 5</strong></li>
    </ul>
    <p>
      In the previous article we started implementing the <code>geo.getEvents</code> Last.FM API call. In this article we will complete the implementation of the <code>Event</code> class and create a small application to display events for a specific location in a compact format.
    </p>
    <h3>
      Implementing the <code>&lt;event/&gt;</code> XML chunk extractor
    </h3>
    <p>
      So let's move on to the WWW::LastFM::Response::Event class. This is one of the core classes for our particular problem domain. Everything up to here has just been stuff needed to build an extensible framework for sending our queries to the Last.FM API and extract values from the response data. Let's get on with it. Create <code>lib/WWW/LastFM/Response/Event.pm</code> with this content:
    </p>
    <pre>
<code>package WWW::LastFM::Response::Event;
use XML::Rabbit;

use HTML::FormatText;
use DateTime::Format::HTTP;

# Last.FM-specific data
has_xpath_value 'id'  =&gt; './id';
has_xpath_value 'tag' =&gt; './tag';
has_xpath_value 'url' =&gt; './url';

# The actual event-related data
has_xpath_value 'title'            =&gt; './title';
has_xpath_value 'description_html' =&gt; './description';
has_xpath_value 'website'          =&gt; './website';
has_xpath_value 'start_date'       =&gt; './startDate';
has_xpath_value 'headliner'        =&gt; './artists/headliner';
has_xpath_value_list '_artists'    =&gt; './artists/artist',
    handles =&gt; {
        'artists'      =&gt; 'elements',
    }
;
has_xpath_object 'venue'     =&gt; './venue'    =&gt; 'WWW::LastFM::Response::Venue';
has_xpath_value_list '_tags' =&gt; './tags/tag',
    handles =&gt; {
        'tags' =&gt; 'elements',
    },
;

# Some various attendance-related data
has_xpath_value 'ticket_count' =&gt; './tickets';
has_xpath_value 'attendance'   =&gt; './attendance';
has_xpath_value 'cancelled'    =&gt; './cancelled';
has_xpath_value 'review_count' =&gt; './reviews';

# Event image URLs
has_xpath_value 'image_s'  =&gt; './image[@size="small"]';
has_xpath_value 'image_m'  =&gt; './image[@size="medium"]';
has_xpath_value 'image_l'  =&gt; './image[@size="large"]';
has_xpath_value 'image_xl' =&gt; './image[@size="extralarge"]';

# The description value, converted to plain text
has 'description' =&gt; (
    is         =&gt; 'ro',
    isa        =&gt; 'Str',
    lazy_build =&gt; 1,
);

sub _build_description {
    my ($self) = @_;
    return HTML::FormatText-&gt;format_string(
        $self-&gt;description_html,
        leftmargin  =&gt;  0,
        rightmargin =&gt; 79,
    );
}

# Convert the start_date to a DateTime class which is much more useful
has 'date' =&gt; (
    is         =&gt; 'ro',
    isa        =&gt; 'DateTime',
    lazy_build =&gt; 1,
);

sub _build_date {
    my ($self) = @_;
    return DateTime::Format::HTTP-&gt;parse_datetime(
        $self-&gt;start_date
    );
}

finalize_class();
</code>
</pre>
    <p>
      There shouldn't be anything surprising in this piece of code. The only thing you haven't seen before is the use of XPath attribute values as selectors. We've already covered all the various declarations, so let's just continue with WWW::LastFM::Response::Venue, so that we can have a look at how to deal with the namespaced latitude/longtitude information. Create <code>lib/WWW/LastFM/Response/Venue.pm</code> with this content:
    </p>
    <pre>
<code>package WWW::LastFM::Response::Venue;
use XML::Rabbit;

# Last.FM-specific data
has_xpath_value 'id'  =&gt; './id';
has_xpath_value 'url' =&gt; './url';

# The actual venue-related data
has_xpath_value 'name'         =&gt; './name';
has_xpath_value 'website'      =&gt; './website';
has_xpath_value 'phone_number' =&gt; './phonenumber';

# Location-related data
has_xpath_value 'city'        =&gt; './location/city';
has_xpath_value 'country'     =&gt; './location/country';
has_xpath_value 'street'      =&gt; './location/street';
has_xpath_value 'postal_code' =&gt; './location/postalcode';
has_xpath_value 'latitude'    =&gt; './location/geo:point/geo:lat';
has_xpath_value 'longitude'   =&gt; './location/geo:point/geo:long';

# Venue image URLs
has_xpath_value 'image_s'  =&gt; './image[@size="small"]';
has_xpath_value 'image_m'  =&gt; './image[@size="medium"]';
has_xpath_value 'image_l'  =&gt; './image[@size="large"]';
has_xpath_value 'image_xl' =&gt; './image[@size="extralarge"]';

# Convenience attribute for combined address
has 'address' =&gt; (
    is         =&gt; 'ro',
    isa        =&gt; 'Str',
    lazy_build =&gt; 1,
);

sub _build_address {
    my ($self) = @_;
    return $self-&gt;name . ', '
         . ( $self-&gt;street ? $self-&gt;street . ', ' : "" )
         . ( $self-&gt;postal_code ? $self-&gt;postal_code . ' ' : "" )
         . $self-&gt;city . ', '
         . $self-&gt;country;
}

finalize_class();
</code>
</pre>
    <p>
      This is just more of the same things we've been doing. Notice the simplicity in dealing with namespaces in the XPath queries for latitude and longitude? It's really that simple.
    </p>
    <h3>
      lastfm_events.pl, a simple WWW::LastFM client
    </h3>
    <p>
      Let's finish it up by making a fairly simple command line app to search for events. Create <code>bin/lastfm_events.pl</code> with the following content:
    </p>
    <pre>
<code>#!/usr/bin/env perl

use strict;
use warnings;
use rlib;
use feature qw(say);

use WWW::LastFM;
use Getopt::Long;

# PODNAME: lastfm_eventss.pl
# ABSTRACT: Search the Last.FM API for events/concerts

STDOUT-&gt;binmode(":utf8");

# Read command line flags
my $limit;
my $distance;
my $help;
GetOptions(
    "limit=i"    =&gt; \$limit,
    "distance=i" =&gt; \$distance,
    "help"       =&gt; \$help,
);

die &lt;&lt;"EOM" if $help;
Usage: $0 [&lt;options&gt;] [&lt;location&gt;]
Options:
    --limit &lt;num&gt;   ; Number of events to return
    --distance &lt;km&gt; ; How large area to search
EOM

my $location = shift;

# Perform query
my $event_list = WWW::LastFM-&gt;new-&gt;geo-&gt;get_events(
    ( defined $limit    ? ( limit    =&gt; $limit )    : () ),
    ( defined $distance ? ( distance =&gt; $distance ) : () ),
    ( defined $location ? ( location =&gt; $location ) : () ),
)-&gt;events;
my @events = $event_list-&gt;all;

# Format and output results
if ( @events &gt; 0 ) {
    foreach my $event ( sort { $a-&gt;date cmp $b-&gt;date } @events ) {
        my $formatted_date = DateTime::Format::HTTP-&gt;format_datetime($event-&gt;date);
        my $headliner = $event-&gt;title ne $event-&gt;headliner ? " with " . $event-&gt;headliner : "";
        my @additional_artists = grep { ! $event-&gt;headliner } $event-&gt;artists;
        say $formatted_date . ": "
          . $event-&gt;title
          . $headliner
          . ( scalar @additional_artists ? " and " . join(", ", @additional_artists ) : "" )
          . " @ " . $event-&gt;venue-&gt;address;
    }
}
else {
    say STDERR "No events found for location '" . $event_list-&gt;location . "'";
}

1;
</code>
</pre>
    <p>
      Let's have some fun with it!
    </p>
    <pre>
<code>$ bin/lastfm_events.pl --distance 50 --limit 50 T&#248;nsberg
Fri, 04 Nov 2011 19:30:00 GMT: Melissa Horn @ Kulturhuset B&#248;lgen, Larvik, Norway
Fri, 18 Nov 2011 19:00:00 GMT: Konsert, Bj&#248;rn Eidsv&#229;g med band  with Bj&#248;rn Eidsv&#229;g @ Parkteatret, Moss, Norway
Fri, 16 Dec 2011 19:00:00 GMT: Helene B&#248;ksle p&#229; Domkirkefestivalen i T&#248;nsberg Domkirke with Helene B&#248;ksle @ T&#248;nsberg Domkirke, T&#248;nsberg, Norway
Fri, 16 Dec 2011 19:31:01 GMT: Ljungblut @ Total, Stoltenbergsgata 46, 3110 T&#248;nsberg, Norway
Sat, 17 Dec 2011 19:34:01 GMT: Ljungblut @ Total, Stoltenbergsgata 46, 3110 T&#248;nsberg, Norway
</code>
</pre>
    <p>
      This shows the events happening around where I live. Not really much to be cheerful about. Hopefully you'll get some more interesting results for where you live.
    </p>
    <h3>
      Conclusion
    </h3>
    <p>
      I wrote this article mostly to showcase how easy it is extract useful information from XML documents with <a href="http://metacpan.org/module/XML::Rabbit">XML::Rabbit</a> and augment it with additional <a href="http://metacpan.org/module/Moose::Manual">Moose</a> magic. I don't really go to concerts very often, but I figured it was a useful example that some of you would find interesting (instead of just using some hand-crafted example XML).
    </p>
    <p>
      If you want to see additional API calls implemented I urge you to <a href="http://robin.smidsrod.no/contact">get in touch</a> with me and help me to improve it. I've shown you how I've done it this far. With some help from various <a href="http://learn.perl.org">Perl learning resources</a> I'm sure you'd be able to whip up a patch with some additional features (or tests). Please <a href="http://twitter.com/robinsmidsrod">follow me on Twitter</a> or <a href="https://github.com/robinsmidsrod">GitHub</a> or subscribe to <a href="http://blog.robin.smidsrod.no/">my blog</a> if you want to stay informed with what I'm doing.
    </p><div class="item_footer"><p><small><a href="http://blog.robin.smidsrod.no/2011/10/04/implementing-www-lastfm-part-5">Original post</a> blogged on <a href="http://b2evolution.net/">b2evolution</a>.</small></p></div>]]></content:encoded>
								<comments>http://blog.robin.smidsrod.no/2011/10/04/implementing-www-lastfm-part-5#comments</comments>
			<wfw:commentRss>http://blog.robin.smidsrod.no/?tempskin=_rss2&#38;disp=comments&#38;p=102</wfw:commentRss>
		</item>
				<item>
			<title>Implementing WWW::LastFM with XML::Rabbit - Part 4</title>
			<link>http://blog.robin.smidsrod.no/2011/10/03/implementing-www-lastfm-part-4</link>
			<pubDate>Mon, 03 Oct 2011 11:24:00 +0000</pubDate>			<dc:creator>Robin Smidsr&#248;d</dc:creator>
			<category domain="main">Perl</category>
<category domain="alt">Software Development</category>			<guid isPermaLink="false">101@http://blog.robin.smidsrod.no/</guid>
						<description>&lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;/2011/09/30/implementing-www-lastfm-part-1&quot;&gt;Part 1 of 5&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;/2011/10/01/implementing-www-lastfm-part-2&quot;&gt;Part 2 of 5&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;/2011/10/02/implementing-www-lastfm-part-3&quot;&gt;Part 3 of 5&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;strong&gt;Part 4 of 5&lt;/strong&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;/2011/10/04/implementing-www-lastfm-part-5&quot;&gt;Part 5 of 5&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
    &lt;p&gt;
      In the previous article we finished implementing the &lt;code&gt;geo.getMetros&lt;/code&gt; Last.FM API call with &lt;a href=&quot;https://metacpan.org/module/XML::Rabbit&quot;&gt;XML::Rabbit&lt;/a&gt;. That particular API call doesn&#039;t include a lot of interesting information, but it is useful to know which worldwide locations the Last.FM service knows about now that we&#039;re going to implement the &lt;code&gt;geo.getEvents&lt;/code&gt; API call.
    &lt;/p&gt;
    &lt;h3&gt;
      Implementing the geo.getEvents API call
    &lt;/h3&gt;
    &lt;p&gt;
      Now that we know which locations are valid, we can actually ask for events for a specific location. Add another method towards the end of WWW::LastFM::API::Geo, like this:
    &lt;/p&gt;
    &lt;pre&gt;
&lt;code&gt;# &lt;a href=&quot;http://www.last.fm/api/show?service=270&quot;&gt;http://www.last.fm/api/show?service=270&lt;/a&gt;
# All strings must be binary encoded utf8 strings
#
# lat (Optional) : Specifies a latitude value to retrieve events for (service returns nearby events by default)
# location (Optional) : Specifies a location to retrieve events for (service returns nearby events by default)
# long (Optional) : Specifies a longitude value to retrieve events for (service returns nearby events by default)
# distance (Optional) : Find events within a specified radius (in kilometres)
# limit (Optional) : The number of results to fetch per page. Defaults to 10.
# page (Optional) : The page number to fetch. Defaults to first page.

sub get_events {
    my ($self, %opts) = @_;
    my $params = &quot;&quot;;
    foreach my $key ( keys %opts ) {
        $params .= &#039;&amp;amp;&#039; . $key . &#039;=&#039; . uri_escape( $opts{$key} );
    }
    my $xml = $self-&amp;gt;lastfm-&amp;gt;get(
           $self-&amp;gt;lastfm-&amp;gt;api_root_url
        . &#039;?method=geo.getEvents&#039;
        . $params
        . &#039;&amp;amp;api_key=&#039; . $self-&amp;gt;lastfm-&amp;gt;api_key
    );
    return WWW::LastFM::Response-&amp;gt;new( xml =&amp;gt; $xml );
}
&lt;/code&gt;
&lt;/pre&gt;
    &lt;p&gt;
      If you&#039;ve paid attention, you will notice that this method is quite similar to &lt;code&gt;get_metros&lt;/code&gt;. We will not go into refactoring it in this article, but it should be quite easy to refactor the common parts into a generic method on the (non-existing) WWW::LastFM::API class and turn that into either a role or a base class for the rest of the WWW::LastFM::API::* classes we might create in the future.
    &lt;/p&gt;
    &lt;h3&gt;
      Implementing the &lt;code&gt;&amp;lt;events/&amp;gt;&lt;/code&gt; XML chunk extractor
    &lt;/h3&gt;
    &lt;p&gt;
      So let&#039;s try it out and see what we get. Again, we will use a fairly simple one-liner.
    &lt;/p&gt;
    &lt;pre&gt;
&lt;code&gt;$ perl -Ilib -MWWW::LastFM -E &#039;STDOUT-&amp;gt;binmode(&quot;:utf8&quot;); say WWW::LastFM-&amp;gt;new-&amp;gt;geo-&amp;gt;get_events( limit =&amp;gt; 1 )-&amp;gt;dump_document_xml()&#039;
&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&amp;gt;
&amp;lt;lfm status=&quot;ok&quot;&amp;gt;
&amp;lt;events xmlns:geo=&quot;http://www.w3.org/2003/01/geo/wgs84_pos#&quot; location=&quot;Oslo, Norway&quot;
        page=&quot;1&quot; perPage=&quot;1&quot; totalPages=&quot;163&quot; total=&quot;163&quot; festivalsonly=&quot;0&quot;&amp;gt;
    &amp;lt;event xmlns:geo=&quot;http://www.w3.org/2003/01/geo/wgs84_pos#&quot;&amp;gt;
  &amp;lt;id&amp;gt;1996187&amp;lt;/id&amp;gt;
  &amp;lt;title&amp;gt;The Jezabels&amp;lt;/title&amp;gt;
  &amp;lt;artists&amp;gt;
    &amp;lt;artist&amp;gt;The Jezabels&amp;lt;/artist&amp;gt;
    &amp;lt;headliner&amp;gt;The Jezabels&amp;lt;/headliner&amp;gt;
  &amp;lt;/artists&amp;gt;
    &amp;lt;venue&amp;gt;
    &amp;lt;id&amp;gt;9014441&amp;lt;/id&amp;gt;
    &amp;lt;name&amp;gt;John Dee&amp;lt;/name&amp;gt;
    &amp;lt;location&amp;gt;
      &amp;lt;city&amp;gt;Oslo&amp;lt;/city&amp;gt;
      &amp;lt;country&amp;gt;Norway&amp;lt;/country&amp;gt;
      &amp;lt;street&amp;gt;Torggata 16&amp;lt;/street&amp;gt;
      &amp;lt;postalcode&amp;gt;0181&amp;lt;/postalcode&amp;gt;
      &amp;lt;geo:point&amp;gt;
         &amp;lt;geo:lat&amp;gt;59.915823&amp;lt;/geo:lat&amp;gt;
         &amp;lt;geo:long&amp;gt;10.751034&amp;lt;/geo:long&amp;gt;
      &amp;lt;/geo:point&amp;gt;
    &amp;lt;/location&amp;gt;
    &amp;lt;url&amp;gt;http://www.last.fm/venue/9014441+John+Dee&amp;lt;/url&amp;gt;
    &amp;lt;website&amp;gt;http://www.rockefeller.no/&amp;lt;/website&amp;gt;
    &amp;lt;phonenumber&amp;gt;22 20 32 32&amp;lt;/phonenumber&amp;gt;
    &amp;lt;image size=&quot;small&quot;/&amp;gt;
        &amp;lt;image size=&quot;medium&quot;/&amp;gt;
        &amp;lt;image size=&quot;large&quot;/&amp;gt;
        &amp;lt;image size=&quot;extralarge&quot;/&amp;gt;
        &amp;lt;image size=&quot;mega&quot;/&amp;gt;
  &amp;lt;/venue&amp;gt;    &amp;lt;startDate&amp;gt;Sun, 25 Sep 2011 20:00:00&amp;lt;/startDate&amp;gt;
  &amp;lt;description&amp;gt;&amp;lt;![CDATA[&amp;lt;div class=&quot;bbcode&quot;&amp;gt;The Jezabels begynte &amp;#229; spille sammen i 2007
  etter &amp;#229; ha m&amp;#248;ttes p&amp;#229; Universitet i Sydney, Australia, og har etter det gitt ut tre
  ep-plater som har f&amp;#229;tt god mottagelse over alt i verden. Bandet har blant annet v&amp;#230;rt
  nominert til Best Breakthrough Artist og Best Single / EP p&amp;#229; Independent Music Awards
  i 2010 og v&amp;#230;rt support til Tegan &amp;amp;amp; Sara. Det er litt vanskelig &amp;#229; beskrive popmusikken
  deres; det er som en blanding mellom indiepop og indierock, med litt disko. Bra er det
  i hvert fall! The Jezabels har ikke gitt ut noen skive i Norge enn&amp;#229;, men hvis du har
  sett Imsdals (forferdlige) reklamefilme, har du h&amp;#248;rt l&amp;#229;ten &amp;#8221;Easy To Love&amp;#8221;. Debutskiven
  kommer under 2011 og den 25. september kan du se bandet p&amp;#229; John DEE!&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;Billetter
  er i salg via Billettservice!&amp;lt;/div&amp;gt;]]&amp;gt;&amp;lt;/description&amp;gt;
  &amp;lt;image size=&quot;small&quot;&amp;gt;http://userserve-ak.last.fm/serve/34/48219593.png&amp;lt;/image&amp;gt;
  &amp;lt;image size=&quot;medium&quot;&amp;gt;http://userserve-ak.last.fm/serve/64/48219593.png&amp;lt;/image&amp;gt;
  &amp;lt;image size=&quot;large&quot;&amp;gt;http://userserve-ak.last.fm/serve/126/48219593.png&amp;lt;/image&amp;gt;
  &amp;lt;image size=&quot;extralarge&quot;&amp;gt;http://userserve-ak.last.fm/serve/252/48219593.png&amp;lt;/image&amp;gt;
  &amp;lt;attendance&amp;gt;6&amp;lt;/attendance&amp;gt;
  &amp;lt;reviews&amp;gt;0&amp;lt;/reviews&amp;gt;
  &amp;lt;tag&amp;gt;lastfm:event=1996187&amp;lt;/tag&amp;gt;
  &amp;lt;url&amp;gt;http://www.last.fm/event/1996187+The+Jezabels&amp;lt;/url&amp;gt;
  &amp;lt;website&amp;gt;https://www.facebook.com/event.php?eid=172860196109875&amp;lt;/website&amp;gt;
    &amp;lt;tickets&amp;gt;
  &amp;lt;/tickets&amp;gt;
    &amp;lt;cancelled&amp;gt;0&amp;lt;/cancelled&amp;gt;
    &amp;lt;tags&amp;gt;
      &amp;lt;tag&amp;gt;australian&amp;lt;/tag&amp;gt;
      &amp;lt;tag&amp;gt;indie&amp;lt;/tag&amp;gt;
      &amp;lt;tag&amp;gt;rock&amp;lt;/tag&amp;gt;
      &amp;lt;tag&amp;gt;female vocalists&amp;lt;/tag&amp;gt;
    &amp;lt;/tags&amp;gt;
    &amp;lt;/event&amp;gt;&amp;lt;/events&amp;gt;&amp;lt;/lfm&amp;gt;
&lt;/code&gt;
&lt;/pre&gt;
    &lt;p&gt;
      Now we&#039;re starting to see what we&#039;re interested in!
    &lt;/p&gt;
    &lt;p&gt;
      But wait, what is that &lt;code&gt;xmlns:geo&lt;/code&gt; attribute at the top there? Will that cause a problem? Yes it will. - That is an XML namespace, and if we want to be able to pull out information from those &lt;code&gt;&amp;lt;geo:point/&amp;gt;&lt;/code&gt; tags we&#039;ll need to specify that we want to deal with this particular namespace. Luckily, dealing with XML namespaces is quite easy with XML::Rabbit. The first thing we need to do is to declare that we want to create queries that use this namespace. Add the following piece of code before the &lt;code&gt;BUILD&lt;/code&gt; method in &lt;code&gt;lib/WWW/LastFM/Response.pm&lt;/code&gt; to take care of that challenge:
    &lt;/p&gt;
    &lt;pre&gt;
&lt;code&gt;add_xpath_namespace &#039;geo&#039; =&amp;gt; &#039;http://www.w3.org/2003/01/geo/wgs84_pos#&#039;;
&lt;/code&gt;
&lt;/pre&gt;
    &lt;p&gt;
      Now we can add a new accessor for the events in WWW::LastFM::Response. Add the following piece of code at the bottom of &lt;code&gt;lib/WWW/LastFM/Response.pm&lt;/code&gt;:
    &lt;/p&gt;
    &lt;pre&gt;
&lt;code&gt;has_xpath_object &#039;events&#039; =&amp;gt; &#039;/lfm/events&#039; =&amp;gt; &#039;WWW::LastFM::Response::EventList&#039;;
&lt;/code&gt;
&lt;/pre&gt;
    &lt;p&gt;
      As this points to another class, we&#039;ll have to create that one too. The contents of &lt;code&gt;lib/WWW/LastFM/Response/EventList.pm&lt;/code&gt; should be this:
    &lt;/p&gt;
    &lt;pre&gt;
&lt;code&gt;package WWW::LastFM::Response::EventList;
use XML::Rabbit;

has_xpath_value &#039;page&#039;       =&amp;gt; &#039;./@page&#039;;
has_xpath_value &#039;page_limit&#039; =&amp;gt; &#039;./@perPage&#039;;
has_xpath_value &#039;page_count&#039; =&amp;gt; &#039;./@totalPages&#039;;

has_xpath_value &#039;location&#039;       =&amp;gt; &#039;./@location&#039;;
has_xpath_value &#039;festivals_only&#039; =&amp;gt; &#039;./@festivalsonly&#039;;

has_xpath_object_map &#039;map&#039; =&amp;gt; &#039;./event&#039;,
    &#039;./id&#039;  =&amp;gt; &#039;WWW::LastFM::Response::Event&#039;,
    handles =&amp;gt; {
        &#039;get&#039; =&amp;gt; &#039;get&#039;,
        &#039;ids&#039; =&amp;gt; &#039;keys&#039;,
        &#039;all&#039; =&amp;gt; &#039;values&#039;,
    },
;

finalize_class();
&lt;/code&gt;
&lt;/pre&gt;
    &lt;p&gt;
      Everything here should be easy to understand, as we&#039;re only declaring simple values. The &lt;code&gt;has_xpath_object_map&lt;/code&gt; declaration might require some clarification, though. We&#039;re declaring a Moose attribute called &lt;code&gt;map&lt;/code&gt; which is a hash reference where the event id from the XML document is the key, and an object of class WWW::LastFM::Response::Event is the value. The native trait delegations for hashes are somewhat different than the ones for arrays. As this object is not designed for data modification, we&#039;ve only added the typical read accessors. Whether you will use &lt;code&gt;ids&lt;/code&gt; and &lt;code&gt;get&lt;/code&gt; or &lt;code&gt;all&lt;/code&gt; depends a bit on your application needs. We&#039;re only going to use &lt;code&gt;all&lt;/code&gt; in our application.
    &lt;/p&gt;
    &lt;p&gt;
      In the next and final article we&#039;ll flesh out the implementation of the &lt;code&gt;Event&lt;/code&gt; class. We will also create a small application that allows us to display events for a specific location in a compact format. See you soon!
    &lt;/p&gt;&lt;div class=&quot;item_footer&quot;&gt;&lt;p&gt;&lt;small&gt;&lt;a href=&quot;http://blog.robin.smidsrod.no/2011/10/03/implementing-www-lastfm-part-4&quot;&gt;Original post&lt;/a&gt; blogged on &lt;a href=&quot;http://b2evolution.net/&quot;&gt;b2evolution&lt;/a&gt;.&lt;/small&gt;&lt;/p&gt;&lt;/div&gt;</description>
			<content:encoded><![CDATA[<ul>
      <li><a href="http://blog.robin.smidsrod.no/2011/09/30/implementing-www-lastfm-part-1">Part 1 of 5</a></li>
      <li><a href="http://blog.robin.smidsrod.no/2011/10/01/implementing-www-lastfm-part-2">Part 2 of 5</a></li>
      <li><a href="http://blog.robin.smidsrod.no/2011/10/02/implementing-www-lastfm-part-3">Part 3 of 5</a></li>
      <li><strong>Part 4 of 5</strong></li>
      <li><a href="http://blog.robin.smidsrod.no/2011/10/04/implementing-www-lastfm-part-5">Part 5 of 5</a></li>
    </ul>
    <p>
      In the previous article we finished implementing the <code>geo.getMetros</code> Last.FM API call with <a href="https://metacpan.org/module/XML::Rabbit">XML::Rabbit</a>. That particular API call doesn't include a lot of interesting information, but it is useful to know which worldwide locations the Last.FM service knows about now that we're going to implement the <code>geo.getEvents</code> API call.
    </p>
    <h3>
      Implementing the geo.getEvents API call
    </h3>
    <p>
      Now that we know which locations are valid, we can actually ask for events for a specific location. Add another method towards the end of WWW::LastFM::API::Geo, like this:
    </p>
    <pre>
<code># <a href="http://www.last.fm/api/show?service=270">http://www.last.fm/api/show?service=270</a>
# All strings must be binary encoded utf8 strings
#
# lat (Optional) : Specifies a latitude value to retrieve events for (service returns nearby events by default)
# location (Optional) : Specifies a location to retrieve events for (service returns nearby events by default)
# long (Optional) : Specifies a longitude value to retrieve events for (service returns nearby events by default)
# distance (Optional) : Find events within a specified radius (in kilometres)
# limit (Optional) : The number of results to fetch per page. Defaults to 10.
# page (Optional) : The page number to fetch. Defaults to first page.

sub get_events {
    my ($self, %opts) = @_;
    my $params = "";
    foreach my $key ( keys %opts ) {
        $params .= '&amp;' . $key . '=' . uri_escape( $opts{$key} );
    }
    my $xml = $self-&gt;lastfm-&gt;get(
           $self-&gt;lastfm-&gt;api_root_url
        . '?method=geo.getEvents'
        . $params
        . '&amp;api_key=' . $self-&gt;lastfm-&gt;api_key
    );
    return WWW::LastFM::Response-&gt;new( xml =&gt; $xml );
}
</code>
</pre>
    <p>
      If you've paid attention, you will notice that this method is quite similar to <code>get_metros</code>. We will not go into refactoring it in this article, but it should be quite easy to refactor the common parts into a generic method on the (non-existing) WWW::LastFM::API class and turn that into either a role or a base class for the rest of the WWW::LastFM::API::* classes we might create in the future.
    </p>
    <h3>
      Implementing the <code>&lt;events/&gt;</code> XML chunk extractor
    </h3>
    <p>
      So let's try it out and see what we get. Again, we will use a fairly simple one-liner.
    </p>
    <pre>
<code>$ perl -Ilib -MWWW::LastFM -E 'STDOUT-&gt;binmode(":utf8"); say WWW::LastFM-&gt;new-&gt;geo-&gt;get_events( limit =&gt; 1 )-&gt;dump_document_xml()'
&lt;?xml version="1.0" encoding="utf-8"?&gt;
&lt;lfm status="ok"&gt;
&lt;events xmlns:geo="http://www.w3.org/2003/01/geo/wgs84_pos#" location="Oslo, Norway"
        page="1" perPage="1" totalPages="163" total="163" festivalsonly="0"&gt;
    &lt;event xmlns:geo="http://www.w3.org/2003/01/geo/wgs84_pos#"&gt;
  &lt;id&gt;1996187&lt;/id&gt;
  &lt;title&gt;The Jezabels&lt;/title&gt;
  &lt;artists&gt;
    &lt;artist&gt;The Jezabels&lt;/artist&gt;
    &lt;headliner&gt;The Jezabels&lt;/headliner&gt;
  &lt;/artists&gt;
    &lt;venue&gt;
    &lt;id&gt;9014441&lt;/id&gt;
    &lt;name&gt;John Dee&lt;/name&gt;
    &lt;location&gt;
      &lt;city&gt;Oslo&lt;/city&gt;
      &lt;country&gt;Norway&lt;/country&gt;
      &lt;street&gt;Torggata 16&lt;/street&gt;
      &lt;postalcode&gt;0181&lt;/postalcode&gt;
      &lt;geo:point&gt;
         &lt;geo:lat&gt;59.915823&lt;/geo:lat&gt;
         &lt;geo:long&gt;10.751034&lt;/geo:long&gt;
      &lt;/geo:point&gt;
    &lt;/location&gt;
    &lt;url&gt;http://www.last.fm/venue/9014441+John+Dee&lt;/url&gt;
    &lt;website&gt;http://www.rockefeller.no/&lt;/website&gt;
    &lt;phonenumber&gt;22 20 32 32&lt;/phonenumber&gt;
    &lt;image size="small"/&gt;
        &lt;image size="medium"/&gt;
        &lt;image size="large"/&gt;
        &lt;image size="extralarge"/&gt;
        &lt;image size="mega"/&gt;
  &lt;/venue&gt;    &lt;startDate&gt;Sun, 25 Sep 2011 20:00:00&lt;/startDate&gt;
  &lt;description&gt;&lt;![CDATA[&lt;div class="bbcode"&gt;The Jezabels begynte &#229; spille sammen i 2007
  etter &#229; ha m&#248;ttes p&#229; Universitet i Sydney, Australia, og har etter det gitt ut tre
  ep-plater som har f&#229;tt god mottagelse over alt i verden. Bandet har blant annet v&#230;rt
  nominert til Best Breakthrough Artist og Best Single / EP p&#229; Independent Music Awards
  i 2010 og v&#230;rt support til Tegan &amp;amp; Sara. Det er litt vanskelig &#229; beskrive popmusikken
  deres; det er som en blanding mellom indiepop og indierock, med litt disko. Bra er det
  i hvert fall! The Jezabels har ikke gitt ut noen skive i Norge enn&#229;, men hvis du har
  sett Imsdals (forferdlige) reklamefilme, har du h&#248;rt l&#229;ten &#8221;Easy To Love&#8221;. Debutskiven
  kommer under 2011 og den 25. september kan du se bandet p&#229; John DEE!&lt;br /&gt;&lt;br /&gt;Billetter
  er i salg via Billettservice!&lt;/div&gt;]]&gt;&lt;/description&gt;
  &lt;image size="small"&gt;http://userserve-ak.last.fm/serve/34/48219593.png&lt;/image&gt;
  &lt;image size="medium"&gt;http://userserve-ak.last.fm/serve/64/48219593.png&lt;/image&gt;
  &lt;image size="large"&gt;http://userserve-ak.last.fm/serve/126/48219593.png&lt;/image&gt;
  &lt;image size="extralarge"&gt;http://userserve-ak.last.fm/serve/252/48219593.png&lt;/image&gt;
  &lt;attendance&gt;6&lt;/attendance&gt;
  &lt;reviews&gt;0&lt;/reviews&gt;
  &lt;tag&gt;lastfm:event=1996187&lt;/tag&gt;
  &lt;url&gt;http://www.last.fm/event/1996187+The+Jezabels&lt;/url&gt;
  &lt;website&gt;https://www.facebook.com/event.php?eid=172860196109875&lt;/website&gt;
    &lt;tickets&gt;
  &lt;/tickets&gt;
    &lt;cancelled&gt;0&lt;/cancelled&gt;
    &lt;tags&gt;
      &lt;tag&gt;australian&lt;/tag&gt;
      &lt;tag&gt;indie&lt;/tag&gt;
      &lt;tag&gt;rock&lt;/tag&gt;
      &lt;tag&gt;female vocalists&lt;/tag&gt;
    &lt;/tags&gt;
    &lt;/event&gt;&lt;/events&gt;&lt;/lfm&gt;
</code>
</pre>
    <p>
      Now we're starting to see what we're interested in!
    </p>
    <p>
      But wait, what is that <code>xmlns:geo</code> attribute at the top there? Will that cause a problem? Yes it will. - That is an XML namespace, and if we want to be able to pull out information from those <code>&lt;geo:point/&gt;</code> tags we'll need to specify that we want to deal with this particular namespace. Luckily, dealing with XML namespaces is quite easy with XML::Rabbit. The first thing we need to do is to declare that we want to create queries that use this namespace. Add the following piece of code before the <code>BUILD</code> method in <code>lib/WWW/LastFM/Response.pm</code> to take care of that challenge:
    </p>
    <pre>
<code>add_xpath_namespace 'geo' =&gt; 'http://www.w3.org/2003/01/geo/wgs84_pos#';
</code>
</pre>
    <p>
      Now we can add a new accessor for the events in WWW::LastFM::Response. Add the following piece of code at the bottom of <code>lib/WWW/LastFM/Response.pm</code>:
    </p>
    <pre>
<code>has_xpath_object 'events' =&gt; '/lfm/events' =&gt; 'WWW::LastFM::Response::EventList';
</code>
</pre>
    <p>
      As this points to another class, we'll have to create that one too. The contents of <code>lib/WWW/LastFM/Response/EventList.pm</code> should be this:
    </p>
    <pre>
<code>package WWW::LastFM::Response::EventList;
use XML::Rabbit;

has_xpath_value 'page'       =&gt; './@page';
has_xpath_value 'page_limit' =&gt; './@perPage';
has_xpath_value 'page_count' =&gt; './@totalPages';

has_xpath_value 'location'       =&gt; './@location';
has_xpath_value 'festivals_only' =&gt; './@festivalsonly';

has_xpath_object_map 'map' =&gt; './event',
    './id'  =&gt; 'WWW::LastFM::Response::Event',
    handles =&gt; {
        'get' =&gt; 'get',
        'ids' =&gt; 'keys',
        'all' =&gt; 'values',
    },
;

finalize_class();
</code>
</pre>
    <p>
      Everything here should be easy to understand, as we're only declaring simple values. The <code>has_xpath_object_map</code> declaration might require some clarification, though. We're declaring a Moose attribute called <code>map</code> which is a hash reference where the event id from the XML document is the key, and an object of class WWW::LastFM::Response::Event is the value. The native trait delegations for hashes are somewhat different than the ones for arrays. As this object is not designed for data modification, we've only added the typical read accessors. Whether you will use <code>ids</code> and <code>get</code> or <code>all</code> depends a bit on your application needs. We're only going to use <code>all</code> in our application.
    </p>
    <p>
      In the next and final article we'll flesh out the implementation of the <code>Event</code> class. We will also create a small application that allows us to display events for a specific location in a compact format. See you soon!
    </p><div class="item_footer"><p><small><a href="http://blog.robin.smidsrod.no/2011/10/03/implementing-www-lastfm-part-4">Original post</a> blogged on <a href="http://b2evolution.net/">b2evolution</a>.</small></p></div>]]></content:encoded>
								<comments>http://blog.robin.smidsrod.no/2011/10/03/implementing-www-lastfm-part-4#comments</comments>
			<wfw:commentRss>http://blog.robin.smidsrod.no/?tempskin=_rss2&#38;disp=comments&#38;p=101</wfw:commentRss>
		</item>
				<item>
			<title>Implementing WWW::LastFM with XML::Rabbit - Part 3</title>
			<link>http://blog.robin.smidsrod.no/2011/10/02/implementing-www-lastfm-part-3</link>
			<pubDate>Sun, 02 Oct 2011 15:33:00 +0000</pubDate>			<dc:creator>Robin Smidsr&#248;d</dc:creator>
			<category domain="main">Perl</category>
<category domain="alt">Software Development</category>			<guid isPermaLink="false">100@http://blog.robin.smidsrod.no/</guid>
						<description>&lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;/2011/09/30/implementing-www-lastfm-part-1&quot;&gt;Part 1 of 5&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;/2011/10/01/implementing-www-lastfm-part-2&quot;&gt;Part 2 of 5&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;strong&gt;Part 3 of 5&lt;/strong&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;/2011/10/03/implementing-www-lastfm-part-4&quot;&gt;Part 4 of 5&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;/2011/10/04/implementing-www-lastfm-part-5&quot;&gt;Part 5 of 5&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
    &lt;p&gt;
      In the previous article we looked at how &lt;a href=&quot;https://metacpan.org/module/XML::Rabbit&quot;&gt;XML::Rabbit&lt;/a&gt; does its magic to give us a very compact syntax for creating Moose attributes that mirror simple XML document values. In this article we&#039;ll look at some of the other sugar functions available in XML::Rabbit.
    &lt;/p&gt;
    &lt;h3&gt;
      Implementing the &lt;code&gt;&amp;lt;metros/&amp;gt;&lt;/code&gt; XML chunk extractor
    &lt;/h3&gt;
    &lt;p&gt;
      So let&#039;s move on to the implementation of WWW::LastFM::Response::MetroList. Now we&#039;re starting to get to the juicy parts of the application. Use this code for &lt;code&gt;lib/WWW/LastFM/Response/MetroList.pm&lt;/code&gt;:
    &lt;/p&gt;
    &lt;pre&gt;
&lt;code&gt;package WWW::LastFM::Response::MetroList;
use XML::Rabbit;

use List::MoreUtils qw(uniq);

has_xpath_object_list &#039;_locations&#039; =&amp;gt; &#039;./metro&#039; =&amp;gt; &#039;WWW::LastFM::Response::Metro&#039;,
    handles =&amp;gt; {
        &#039;locations&#039;        =&amp;gt; &#039;elements&#039;,
        &#039;filter_locations&#039; =&amp;gt; &#039;grep&#039;,
    },
;

has_xpath_value_list &#039;_countries_with_duplicates&#039; =&amp;gt; &#039;./metro/country&#039;;

has &#039;_unique_countries&#039; =&amp;gt; (
    is         =&amp;gt; &#039;ro&#039;,
    isa        =&amp;gt; &#039;ArrayRef[Str]&#039;,
    traits     =&amp;gt; [&#039;Array&#039;],
    handles    =&amp;gt; {
        &#039;countries&#039;        =&amp;gt; &#039;elements&#039;,
        &#039;filter_countries&#039; =&amp;gt; &#039;grep&#039;,
    },
    lazy_build =&amp;gt; 1,
);

sub _build__unique_countries {
    my ($self) = @_;
    return [
        uniq(
             @{ $self-&amp;gt;_countries_with_duplicates }
        )
    ];
}

finalize_class();
&lt;/code&gt;
&lt;/pre&gt;
    &lt;p&gt;
      Here you have another declaration method called &lt;code&gt;has_xpath_object_list&lt;/code&gt;. It is similar to &lt;code&gt;has_xpath_object&lt;/code&gt;, which we&#039;ve already covered, but it returns an array of objects instead of just a single object. We also add some &lt;a href=&quot;https://metacpan.org/module/Moose::Manual::Delegation#NATIVE-DELEGATION&quot;&gt;Moose native delegations&lt;/a&gt; for arrays. This makes the API more user friendly, as we don&#039;t need to dereference the array references all the time.
    &lt;/p&gt;
    &lt;p&gt;
      The next declaration is almost the same, but it works with strings instead of objects. As the XML document contains lots of duplicates, we create a separate attribute that takes care of getting rid of the duplicates and presents the API we&#039;re interested in.
    &lt;/p&gt;
    &lt;p&gt;
      Now we complete this with the implementation of WWW::LastFM::Response::Metro. Create the file &lt;code&gt;lib/WWW/LastFM/Response/Metro.pm&lt;/code&gt; with the following content:
    &lt;/p&gt;
    &lt;pre&gt;
&lt;code&gt;package WWW::LastFM::Response::Metro;
use XML::Rabbit;

has_xpath_value &#039;name&#039;    =&amp;gt; &#039;./name&#039;;
has_xpath_value &#039;country&#039; =&amp;gt; &#039;./country&#039;;

has &#039;country_and_name&#039; =&amp;gt; (
    is         =&amp;gt; &#039;ro&#039;,
    isa        =&amp;gt; &#039;Str&#039;,
    lazy_build =&amp;gt; 1,
);

sub _build_country_and_name {
    my ($self) = @_;
    return $self-&amp;gt;country . &quot;: &quot; . $self-&amp;gt;name;
}

has &#039;name_and_country&#039; =&amp;gt; (
    is         =&amp;gt; &#039;ro&#039;,
    isa        =&amp;gt; &#039;Str&#039;,
    lazy_build =&amp;gt; 1,
);

sub _build_name_and_country {
    my ($self) = @_;
    return $self-&amp;gt;name . &quot;, &quot; . $self-&amp;gt;country;
}

finalize_class();
&lt;/code&gt;
&lt;/pre&gt;
    &lt;p&gt;
      This is a pretty straight-forward class. You have two string values from the &lt;code&gt;&amp;lt;metro/&amp;gt;&lt;/code&gt; XML element extracted. Look at the use of relative XPath queries, which avoids having to construct elaborate queries based on the root of the XML document. As a convenience, I added two calculated attributes.
    &lt;/p&gt;
    &lt;p&gt;
      We can still test how it works with a one-liner, but it is starting to get a bit long.
    &lt;/p&gt;
    &lt;pre&gt;
&lt;code&gt;$ perl -Ilib -MWWW::LastFM -E &#039;STDOUT-&amp;gt;binmode(&quot;:utf8&quot;); say join(&quot;\n&quot;, sort map { $_-&amp;gt;name_and_country } WWW::LastFM-&amp;gt;new-&amp;gt;geo-&amp;gt;get_metros-&amp;gt;metros-&amp;gt;filter_locations(sub { $_-&amp;gt;country_and_name =~ /Norway/ }) )&#039;
Bergen, Norway
Oslo, Norway
&lt;/code&gt;
&lt;/pre&gt;
    &lt;h3&gt;
      lastfm_locations.pl, a simple WWW::LastFM client
    &lt;/h3&gt;
    &lt;p&gt;
      Let&#039;s create a script we can use to query locations available in the Last.FM service. Create &lt;code&gt;bin/lastfm_locations.pl&lt;/code&gt; with this content:
    &lt;/p&gt;
    &lt;pre&gt;
&lt;code&gt;#!/usr/bin/env perl

use strict;
use warnings;
use rlib;
use feature qw(say);

use WWW::LastFM;
use Encode qw(decode encode);

my $filter = shift;
my $filter_utf8 = $filter ? Encode::decode(&#039;UTF-8&#039;, $filter) : &quot;&quot;;

my @locations = sort
        map { $_-&amp;gt;name_and_country }
        WWW::LastFM-&amp;gt;new-&amp;gt;geo-&amp;gt;get_metros-&amp;gt;metros-&amp;gt;filter_locations(
            sub { $_-&amp;gt;country_and_name =~ /\Q$filter_utf8\E/i }
        )
;

if ( @locations &amp;gt; 0 ) {
    say Encode::encode(&#039;UTF-8&#039;, join(&quot;\n&quot;, @locations) );
}
else {
    say &quot;No locations found matching &#039;$filter&#039;&quot;;
}
&lt;/code&gt;
&lt;/pre&gt;
    &lt;p&gt;
      This is very similar to the one-liner, but with a bit more error checking and Unicode handling to ensure you can specify command line parameters in UTF8. I guess I&#039;ve forgotten to mention this until now, but I have assumed that you are using a terminal software that uses UTF8 encoding. The only common operating system I know that doesn&#039;t do that by default nowadays is Windows. I&#039;ll leave dealing with that challenge for another article.
    &lt;/p&gt;
    &lt;p&gt;
      You should now have a complete application that allows you to ask a remote HTTP-based API for some information and display it in the way you want to. Adding more API calls is just a matter of adding additional method calls in WWW::LastFM::API::Geo and creating a new response class to handle the XML output. In the next article we will add the API call we&#039;re really interested in, &lt;code&gt;geo.getEvents&lt;/code&gt;. Stay tuned!
    &lt;/p&gt;&lt;div class=&quot;item_footer&quot;&gt;&lt;p&gt;&lt;small&gt;&lt;a href=&quot;http://blog.robin.smidsrod.no/2011/10/02/implementing-www-lastfm-part-3&quot;&gt;Original post&lt;/a&gt; blogged on &lt;a href=&quot;http://b2evolution.net/&quot;&gt;b2evolution&lt;/a&gt;.&lt;/small&gt;&lt;/p&gt;&lt;/div&gt;</description>
			<content:encoded><![CDATA[<ul>
      <li><a href="http://blog.robin.smidsrod.no/2011/09/30/implementing-www-lastfm-part-1">Part 1 of 5</a></li>
      <li><a href="http://blog.robin.smidsrod.no/2011/10/01/implementing-www-lastfm-part-2">Part 2 of 5</a></li>
      <li><strong>Part 3 of 5</strong></li>
      <li><a href="http://blog.robin.smidsrod.no/2011/10/03/implementing-www-lastfm-part-4">Part 4 of 5</a></li>
      <li><a href="http://blog.robin.smidsrod.no/2011/10/04/implementing-www-lastfm-part-5">Part 5 of 5</a></li>
    </ul>
    <p>
      In the previous article we looked at how <a href="https://metacpan.org/module/XML::Rabbit">XML::Rabbit</a> does its magic to give us a very compact syntax for creating Moose attributes that mirror simple XML document values. In this article we'll look at some of the other sugar functions available in XML::Rabbit.
    </p>
    <h3>
      Implementing the <code>&lt;metros/&gt;</code> XML chunk extractor
    </h3>
    <p>
      So let's move on to the implementation of WWW::LastFM::Response::MetroList. Now we're starting to get to the juicy parts of the application. Use this code for <code>lib/WWW/LastFM/Response/MetroList.pm</code>:
    </p>
    <pre>
<code>package WWW::LastFM::Response::MetroList;
use XML::Rabbit;

use List::MoreUtils qw(uniq);

has_xpath_object_list '_locations' =&gt; './metro' =&gt; 'WWW::LastFM::Response::Metro',
    handles =&gt; {
        'locations'        =&gt; 'elements',
        'filter_locations' =&gt; 'grep',
    },
;

has_xpath_value_list '_countries_with_duplicates' =&gt; './metro/country';

has '_unique_countries' =&gt; (
    is         =&gt; 'ro',
    isa        =&gt; 'ArrayRef[Str]',
    traits     =&gt; ['Array'],
    handles    =&gt; {
        'countries'        =&gt; 'elements',
        'filter_countries' =&gt; 'grep',
    },
    lazy_build =&gt; 1,
);

sub _build__unique_countries {
    my ($self) = @_;
    return [
        uniq(
             @{ $self-&gt;_countries_with_duplicates }
        )
    ];
}

finalize_class();
</code>
</pre>
    <p>
      Here you have another declaration method called <code>has_xpath_object_list</code>. It is similar to <code>has_xpath_object</code>, which we've already covered, but it returns an array of objects instead of just a single object. We also add some <a href="https://metacpan.org/module/Moose::Manual::Delegation#NATIVE-DELEGATION">Moose native delegations</a> for arrays. This makes the API more user friendly, as we don't need to dereference the array references all the time.
    </p>
    <p>
      The next declaration is almost the same, but it works with strings instead of objects. As the XML document contains lots of duplicates, we create a separate attribute that takes care of getting rid of the duplicates and presents the API we're interested in.
    </p>
    <p>
      Now we complete this with the implementation of WWW::LastFM::Response::Metro. Create the file <code>lib/WWW/LastFM/Response/Metro.pm</code> with the following content:
    </p>
    <pre>
<code>package WWW::LastFM::Response::Metro;
use XML::Rabbit;

has_xpath_value 'name'    =&gt; './name';
has_xpath_value 'country' =&gt; './country';

has 'country_and_name' =&gt; (
    is         =&gt; 'ro',
    isa        =&gt; 'Str',
    lazy_build =&gt; 1,
);

sub _build_country_and_name {
    my ($self) = @_;
    return $self-&gt;country . ": " . $self-&gt;name;
}

has 'name_and_country' =&gt; (
    is         =&gt; 'ro',
    isa        =&gt; 'Str',
    lazy_build =&gt; 1,
);

sub _build_name_and_country {
    my ($self) = @_;
    return $self-&gt;name . ", " . $self-&gt;country;
}

finalize_class();
</code>
</pre>
    <p>
      This is a pretty straight-forward class. You have two string values from the <code>&lt;metro/&gt;</code> XML element extracted. Look at the use of relative XPath queries, which avoids having to construct elaborate queries based on the root of the XML document. As a convenience, I added two calculated attributes.
    </p>
    <p>
      We can still test how it works with a one-liner, but it is starting to get a bit long.
    </p>
    <pre>
<code>$ perl -Ilib -MWWW::LastFM -E 'STDOUT-&gt;binmode(":utf8"); say join("\n", sort map { $_-&gt;name_and_country } WWW::LastFM-&gt;new-&gt;geo-&gt;get_metros-&gt;metros-&gt;filter_locations(sub { $_-&gt;country_and_name =~ /Norway/ }) )'
Bergen, Norway
Oslo, Norway
</code>
</pre>
    <h3>
      lastfm_locations.pl, a simple WWW::LastFM client
    </h3>
    <p>
      Let's create a script we can use to query locations available in the Last.FM service. Create <code>bin/lastfm_locations.pl</code> with this content:
    </p>
    <pre>
<code>#!/usr/bin/env perl

use strict;
use warnings;
use rlib;
use feature qw(say);

use WWW::LastFM;
use Encode qw(decode encode);

my $filter = shift;
my $filter_utf8 = $filter ? Encode::decode('UTF-8', $filter) : "";

my @locations = sort
        map { $_-&gt;name_and_country }
        WWW::LastFM-&gt;new-&gt;geo-&gt;get_metros-&gt;metros-&gt;filter_locations(
            sub { $_-&gt;country_and_name =~ /\Q$filter_utf8\E/i }
        )
;

if ( @locations &gt; 0 ) {
    say Encode::encode('UTF-8', join("\n", @locations) );
}
else {
    say "No locations found matching '$filter'";
}
</code>
</pre>
    <p>
      This is very similar to the one-liner, but with a bit more error checking and Unicode handling to ensure you can specify command line parameters in UTF8. I guess I've forgotten to mention this until now, but I have assumed that you are using a terminal software that uses UTF8 encoding. The only common operating system I know that doesn't do that by default nowadays is Windows. I'll leave dealing with that challenge for another article.
    </p>
    <p>
      You should now have a complete application that allows you to ask a remote HTTP-based API for some information and display it in the way you want to. Adding more API calls is just a matter of adding additional method calls in WWW::LastFM::API::Geo and creating a new response class to handle the XML output. In the next article we will add the API call we're really interested in, <code>geo.getEvents</code>. Stay tuned!
    </p><div class="item_footer"><p><small><a href="http://blog.robin.smidsrod.no/2011/10/02/implementing-www-lastfm-part-3">Original post</a> blogged on <a href="http://b2evolution.net/">b2evolution</a>.</small></p></div>]]></content:encoded>
								<comments>http://blog.robin.smidsrod.no/2011/10/02/implementing-www-lastfm-part-3#comments</comments>
			<wfw:commentRss>http://blog.robin.smidsrod.no/?tempskin=_rss2&#38;disp=comments&#38;p=100</wfw:commentRss>
		</item>
				<item>
			<title>Implementing WWW::LastFM with XML::Rabbit - Part 2</title>
			<link>http://blog.robin.smidsrod.no/2011/10/01/implementing-www-lastfm-part-2</link>
			<pubDate>Sat, 01 Oct 2011 18:09:00 +0000</pubDate>			<dc:creator>Robin Smidsr&#248;d</dc:creator>
			<category domain="main">Perl</category>
<category domain="alt">Software Development</category>			<guid isPermaLink="false">99@http://blog.robin.smidsrod.no/</guid>
						<description>&lt;ul&gt;
      &lt;li&gt;&lt;a href=&quot;/2011/09/30/implementing-www-lastfm-part-1&quot;&gt;Part 1 of 5&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;strong&gt;Part 2 of 5&lt;/strong&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;/2011/10/02/implementing-www-lastfm-part-3&quot;&gt;Part 3 of 5&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;/2011/10/03/implementing-www-lastfm-part-4&quot;&gt;Part 4 of 5&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;/2011/10/04/implementing-www-lastfm-part-5&quot;&gt;Part 5 of 5&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
    &lt;p&gt;
      In the previous article we created a simple framework for making HTTP requests to the Last.FM API. In this article we&#039;ll go into detail on how &lt;a href=&quot;https://metacpan.org/module/XML::Rabbit&quot;&gt;XML::Rabbit&lt;/a&gt; can help us to extract the information we want from the XML output we saw in the previous article. I&#039;ve added that XML output here, for your convenience.
    &lt;/p&gt;
    &lt;pre&gt;
&lt;code&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&amp;gt;
&amp;lt;lfm status=&quot;ok&quot;&amp;gt;
&amp;lt;metros&amp;gt;
    &amp;lt;metro&amp;gt;
        &amp;lt;name&amp;gt;Sydney&amp;lt;/name&amp;gt;
                &amp;lt;country&amp;gt;Australia&amp;lt;/country&amp;gt;
    &amp;lt;/metro&amp;gt;
...snip...
    &amp;lt;metro&amp;gt;
        &amp;lt;name&amp;gt;Wichita&amp;lt;/name&amp;gt;
                &amp;lt;country&amp;gt;United States&amp;lt;/country&amp;gt;
    &amp;lt;/metro&amp;gt;
&amp;lt;/metros&amp;gt;&amp;lt;/lfm&amp;gt; 
&lt;/code&gt;
&lt;/pre&gt;
    &lt;h3&gt;
      Implementing the geo.getMetros API call
    &lt;/h3&gt;
    &lt;p&gt;
      Add the following code at the top of &lt;code&gt;lib/WWW/LastFM.pm&lt;/code&gt; to load the WWW::LastFM::API::Geo module (which we&#039;ll flesh out in a moment).
    &lt;/p&gt;
    &lt;pre&gt;
&lt;code&gt;use WWW::LastFM::API::Geo;
&lt;/code&gt;
&lt;/pre&gt;
    &lt;p&gt;
      Next we add another attribute so we can get easy access to the Geo class. Add at the bottom:
    &lt;/p&gt;
    &lt;pre&gt;
&lt;code&gt;# API modules
has &#039;geo&#039; =&amp;gt; (
    is         =&amp;gt; &#039;ro&#039;,
    isa        =&amp;gt; &#039;WWW::LastFM::API::Geo&#039;,
    lazy_build =&amp;gt; 1,
);

sub _build_geo {
    my ($self) = @_;
    return WWW::LastFM::API::Geo-&amp;gt;new( lastfm =&amp;gt; $self );
}
&lt;/code&gt;
&lt;/pre&gt;
    &lt;p&gt;
      The next part is to create the basic skeleton that allows us to make API calls with convenience. Add the following code to &lt;code&gt;lib/WWW/LastFM/API/Geo.pm&lt;/code&gt;:
    &lt;/p&gt;
    &lt;pre&gt;
&lt;code&gt;package WWW::LastFM::API::Geo;
use Moose;
use namespace::autoclean;

use URI::Escape;

has &#039;lastfm&#039; =&amp;gt; (
    is       =&amp;gt; &#039;ro&#039;,
    isa      =&amp;gt; &#039;WWW::LastFM&#039;,
    required =&amp;gt; 1,
);

# &lt;a href=&quot;http://www.last.fm/api/show?service=435&quot;&gt;http://www.last.fm/api/show?service=435&lt;/a&gt;
# $country must be a binary encoded utf8 string

sub get_metros {
    my ($self, $country) = @_;
    return $self-&amp;gt;lastfm-&amp;gt;get(
           $self-&amp;gt;lastfm-&amp;gt;api_root_url
        . &#039;?method=geo.getMetros&#039;
        . ( $country ? &#039;&amp;amp;country=&#039; . uri_escape($country) : &quot;&quot; )
        . &#039;&amp;amp;api_key=&#039; . $self-&amp;gt;lastfm-&amp;gt;api_key
    );
}

__PACKAGE__-&amp;gt;meta-&amp;gt;make_immutable();

1;
&lt;/code&gt;
&lt;/pre&gt;
    &lt;p&gt;
      We perform another one-liner to verify that we&#039;re on the right track.
    &lt;/p&gt;
    &lt;pre&gt;
&lt;code&gt;$ perl -Ilib -MWWW::LastFM -E &#039;say WWW::LastFM-&amp;gt;new-&amp;gt;geo-&amp;gt;get_metros&#039;
&lt;/code&gt;
&lt;/pre&gt;
    &lt;p&gt;
      What is interesting to see is that we&#039;ve forwarded the instance of &lt;code&gt;WWW::LastFM&lt;/code&gt; into the API class to avoid using global variables to hold our shared state. But let&#039;s see if we can&#039;t do something with that ugly XML.
    &lt;/p&gt;
    &lt;h3&gt;
      Dealing with the root element in the response XML
    &lt;/h3&gt;
    &lt;p&gt;
      Let&#039;s have a closer look at the XML returned.
    &lt;/p&gt;
    &lt;pre&gt;
&lt;code&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&amp;gt;
&amp;lt;lfm status=&quot;ok&quot;&amp;gt;
&amp;lt;metros&amp;gt;
    &amp;lt;metro&amp;gt;
        &amp;lt;name&amp;gt;Sydney&amp;lt;/name&amp;gt;
                &amp;lt;country&amp;gt;Australia&amp;lt;/country&amp;gt;
    &amp;lt;/metro&amp;gt;
... &amp;lt;snip&amp;gt; ...
    &amp;lt;metro&amp;gt;
        &amp;lt;name&amp;gt;Wichita&amp;lt;/name&amp;gt;
                &amp;lt;country&amp;gt;United States&amp;lt;/country&amp;gt;
    &amp;lt;/metro&amp;gt;
&amp;lt;/metros&amp;gt;&amp;lt;/lfm&amp;gt;
&lt;/code&gt;
&lt;/pre&gt;
    &lt;p&gt;
      What we can see is that Last.FM always wraps its response in a &lt;code&gt;&amp;lt;lfm/&amp;gt;&lt;/code&gt; root element, as described in &lt;a href=&quot;http://www.last.fm/api/rest&quot;&gt;API REST requests&lt;/a&gt;. It holds one attribute named &lt;code&gt;status&lt;/code&gt;, and it&#039;s value is either &lt;code&gt;ok&lt;/code&gt; or &lt;code&gt;failed&lt;/code&gt;. So we&#039;re dealing with a boolean here. If the request failed it will contain an error code, like this:
    &lt;/p&gt;
    &lt;pre&gt;
&lt;code&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&amp;gt;
&amp;lt;lfm status=&quot;failed&quot;&amp;gt;
    &amp;lt;error code=&quot;10&quot;&amp;gt;Invalid API Key&amp;lt;/error&amp;gt;
&amp;lt;/lfm&amp;gt;
&lt;/code&gt;
&lt;/pre&gt;
    &lt;p&gt;
      So let&#039;s see if we can encapsulate this behaviour in a class with a nice API. Let&#039;s first change our &lt;code&gt;get_metros&lt;/code&gt; method to return this response class instead of some boring XML. Edit &lt;code&gt;lib/WWW/LastFM/API/Geo.pm&lt;/code&gt; and add this after the other imports:
    &lt;/p&gt;
    &lt;pre&gt;
&lt;code&gt;use WWW::LastFM::Response;
&lt;/code&gt;
&lt;/pre&gt;
    &lt;p&gt;
      And change the &lt;code&gt;get_metros&lt;/code&gt; method into this:
    &lt;/p&gt;
    &lt;pre&gt;
&lt;code&gt;sub get_metros {
    my ($self, $country) = @_;
    my $xml = $self-&amp;gt;lastfm-&amp;gt;get(
           $self-&amp;gt;lastfm-&amp;gt;api_root_url
        . &#039;?method=geo.getMetros&#039;
        . ( $country ? &#039;&amp;amp;country=&#039; . uri_escape($country) : &quot;&quot; )
        . &#039;&amp;amp;api_key=&#039; . $self-&amp;gt;lastfm-&amp;gt;api_key
    );
    return WWW::LastFM::Response-&amp;gt;new( xml =&amp;gt; $xml );
}
&lt;/code&gt;
&lt;/pre&gt;
    &lt;p&gt;
      So what should this WWW::LastFM::Response class look like? What we know is that it should require some XML to work on, and if it doesn&#039;t get that it should blow up with an error message (we want it to throw an exception of some kind). We also want to know if the response failed or not, and if it failed, we should throw an exception as well. If everything is okay, we should be able to get to those metros. Create &lt;code&gt;lib/WWW/LastFM/Response.pm&lt;/code&gt; with this content:
    &lt;/p&gt;
    &lt;pre&gt;
&lt;code&gt;package WWW::LastFM::Response;
use XML::Rabbit::Root 0.1.0;

sub BUILD {
    my ($self) = @_;
    return if $self-&amp;gt;is_success;
    confess(&quot;Last.FM response error &quot; . $self-&amp;gt;error_code . &quot;: &quot; . $self-&amp;gt;error);
}

has &#039;is_success&#039; =&amp;gt; (
    is         =&amp;gt; &#039;ro&#039;,
    isa        =&amp;gt; &#039;Bool&#039;,
    lazy_build =&amp;gt; 1,
);

sub _build_is_success {
    my ($self) = @_;
    return unless $self-&amp;gt;status eq &#039;ok&#039;;
    return 1;
}

has_xpath_value &#039;status&#039;     =&amp;gt; &#039;/lfm/@status&#039;;
has_xpath_value &#039;error&#039;      =&amp;gt; &#039;/lfm/error&#039;;
has_xpath_value &#039;error_code&#039; =&amp;gt; &#039;/lfm/error/@code&#039;;

has_xpath_object &#039;metros&#039; =&amp;gt; &#039;/lfm/metros&#039; =&amp;gt; &#039;WWW::LastFM::Response::MetroList&#039;;

finalize_class();
&lt;/code&gt;
&lt;/pre&gt;
    &lt;p&gt;
      You can test it immediately to see if it works as expected:
    &lt;/p&gt;
    &lt;pre&gt;
&lt;code&gt;$ perl -Ilib -MWWW::LastFM -E &#039;say WWW::LastFM-&amp;gt;new-&amp;gt;geo-&amp;gt;get_metros-&amp;gt;status&#039;
&lt;/code&gt;
&lt;/pre&gt;
    &lt;p&gt;
      You should get a string that says &lt;code&gt;ok&lt;/code&gt; (unless you get an error because the Last.FM API server refuses to answer). If you want to force an error condition, try it out with an invalid API key.
    &lt;/p&gt;
    &lt;pre&gt;
&lt;code&gt;$ perl -Ilib -MWWW::LastFM -E &#039;say WWW::LastFM-&amp;gt;new( api_key =&amp;gt; &quot;1234&quot; )-&amp;gt;geo-&amp;gt;get_metros-&amp;gt;status&#039;
Last.FM response error 10: Invalid API key - You must be granted a valid key by last.fm at ...
&lt;/code&gt;
&lt;/pre&gt;
    &lt;p&gt;
      I snipped the rest of the stack trace, as it is of no interest at this point. We know where we made a mistake.
    &lt;/p&gt;
    &lt;h3&gt;
      How XML::Rabbit::Root simplifies dealing with the XML document data
    &lt;/h3&gt;
    &lt;p&gt;
      Let&#039;s get into detail on how that class definition works.
    &lt;/p&gt;
    &lt;p&gt;
      Using XML::Rabbit::Root does a whole lot of things behind the scenes. It is the equivalent of doing this:
    &lt;/p&gt;
    &lt;pre&gt;
&lt;code&gt;use Moose;
with &quot;XML::Rabbit::RootNode&quot;;
use namespace::autoclean;
use XML::Rabbit::Sugar;
&lt;/code&gt;
&lt;/pre&gt;
    &lt;p&gt;
      The &lt;code&gt;finalize_class()&lt;/code&gt; at the bottom is the equivalent of &lt;code&gt;__PACKAGE__-&amp;gt;meta-&amp;gt;make_immutable(); 1;&lt;/code&gt;, which ensures the class executes as fast as possible during runtime and that the file loaded returns a true value, as required by the perl parser. So with that out of the way, let&#039;s take a look at the meat of the class and what that imported sugar, &lt;code&gt;has_xpath_value&lt;/code&gt; and &lt;code&gt;has_xpath_object&lt;/code&gt;, represents.
    &lt;/p&gt;
    &lt;p&gt;
      The first thing to notice is that there is no &lt;code&gt;xml&lt;/code&gt; attribute declared in the class. This parameter comes from &lt;a href=&quot;https://metacpan.org/module/XML::Rabbit::Role::Document&quot;&gt;XML::Rabbit::Role::Document&lt;/a&gt;. It is automatically available, and you must specify either &lt;code&gt;file&lt;/code&gt;, &lt;code&gt;fh&lt;/code&gt;, &lt;code&gt;xml&lt;/code&gt; or &lt;code&gt;dom&lt;/code&gt;, based on what kind of format your XML document is in. As we have it in a string, we use the &lt;code&gt;xml&lt;/code&gt; parameter.
    &lt;/p&gt;
    &lt;p&gt;
      The &lt;code&gt;BUILD&lt;/code&gt; method is a special method Moose will automatically call after it has constructed an instance. In our case, we use it to verify the state of our instance. If the attribute &lt;code&gt;is_success&lt;/code&gt; is true we just return, as everything is okay. If it is not, we throw an exception with the error code and text from the XML. This is how we can easily make an unwanted value in an attribute cause the entire construction of the object to fail.
    &lt;/p&gt;
    &lt;p&gt;
      The &lt;code&gt;is_success&lt;/code&gt; attribute is pretty straight-forward. We take a string value that represents the status and checks if it is negative (that is, NOT the value &lt;code&gt;ok&lt;/code&gt;). If that is the case, we return false, otherwise things must be okay and we can return a true value. Notice that I use a &lt;a href=&quot;http://c2.com/cgi/wiki?GuardClause&quot;&gt;guard clause&lt;/a&gt; to fail early. This is a best practice to avoid the dreaded &lt;a href=&quot;http://c2.com/cgi/wiki?ArrowAntiPattern&quot;&gt;arrow anti-pattern&lt;/a&gt;.
    &lt;/p&gt;
    &lt;p&gt;
      So what does the &lt;code&gt;has_xpath_value &#039;status&#039; =&amp;gt; &#039;//lfm/@status&#039;;&lt;/code&gt; declaration actually mean? Let&#039;s have a look in &lt;a href=&quot;https://metacpan.org/module/XML::Rabbit::Sugar&quot;&gt;XML::Rabbit::Sugar&lt;/a&gt; and see if we can&#039;t figure it out. It says:
    &lt;/p&gt;
    &lt;blockquote&gt;
      &lt;pre&gt;
&lt;code&gt;has_xpath_value($attr_name, $xpath_query, @moose_params)
&lt;/code&gt;
&lt;/pre&gt;
      &lt;p&gt;
        Extracts a single string according to the xpath query specified. The attribute &lt;code&gt;isa&lt;/code&gt; parameter is automatically set to &lt;code&gt;Str&lt;/code&gt;. The attribute native trait is automatically set to &lt;code&gt;String&lt;/code&gt;.
      &lt;/p&gt;
    &lt;/blockquote&gt;
    &lt;p&gt;
      Okay, so this code creates a Moose attribute with an &lt;code&gt;isa&lt;/code&gt; parameter of &lt;code&gt;Str&lt;/code&gt; that will actually represent the value of the &lt;code&gt;status&lt;/code&gt; attribute on the &lt;code&gt;lfm&lt;/code&gt; root element in the XML document. Take a look at an &lt;a href=&quot;http://zvon.org/comp/r/tut-XPath_1.html#Pages~List_of_XPaths&quot;&gt;XPath tutorial&lt;/a&gt; if you&#039;re unfamiliar with the syntax. If you need some additional Moose attribute parameters, you can specify them at the end. In our case there is no need for any. The &lt;code&gt;error&lt;/code&gt; and &lt;code&gt;error_code&lt;/code&gt; attribute is just the same thing.
    &lt;/p&gt;
    &lt;p&gt;
      So what is this &lt;code&gt;has_xpath_object&lt;/code&gt; thing? It represents exactly the same thing as &lt;code&gt;has_xpath_value&lt;/code&gt;, but instead of being a string value, it is an object of the specified class. The &lt;code&gt;metros&lt;/code&gt; attribute will represent the list of metros returned in the XML document. I was thinking about naming the attribute &lt;code&gt;metro_list&lt;/code&gt;, to avoid nouns in plural, but decided to keep it in plural because it more closely matches the XML document layout. To avoid putting too much responsibility inside WWW::LastFM::Response, I&#039;ve delegated dealing with this list of metros to another class. This follows object orientation best practices, which states that each class should have a clear and defined purpose.
    &lt;/p&gt;
    &lt;p&gt;
      In the next article we&#039;ll dive into the details on how this &lt;code&gt;MetroList&lt;/code&gt; class is implemented.
    &lt;/p&gt;&lt;div class=&quot;item_footer&quot;&gt;&lt;p&gt;&lt;small&gt;&lt;a href=&quot;http://blog.robin.smidsrod.no/2011/10/01/implementing-www-lastfm-part-2&quot;&gt;Original post&lt;/a&gt; blogged on &lt;a href=&quot;http://b2evolution.net/&quot;&gt;b2evolution&lt;/a&gt;.&lt;/small&gt;&lt;/p&gt;&lt;/div&gt;</description>
			<content:encoded><![CDATA[<ul>
      <li><a href="http://blog.robin.smidsrod.no/2011/09/30/implementing-www-lastfm-part-1">Part 1 of 5</a></li>
      <li><strong>Part 2 of 5</strong></li>
      <li><a href="http://blog.robin.smidsrod.no/2011/10/02/implementing-www-lastfm-part-3">Part 3 of 5</a></li>
      <li><a href="http://blog.robin.smidsrod.no/2011/10/03/implementing-www-lastfm-part-4">Part 4 of 5</a></li>
      <li><a href="http://blog.robin.smidsrod.no/2011/10/04/implementing-www-lastfm-part-5">Part 5 of 5</a></li>
    </ul>
    <p>
      In the previous article we created a simple framework for making HTTP requests to the Last.FM API. In this article we'll go into detail on how <a href="https://metacpan.org/module/XML::Rabbit">XML::Rabbit</a> can help us to extract the information we want from the XML output we saw in the previous article. I've added that XML output here, for your convenience.
    </p>
    <pre>
<code>&lt;?xml version="1.0" encoding="utf-8"?&gt;
&lt;lfm status="ok"&gt;
&lt;metros&gt;
    &lt;metro&gt;
        &lt;name&gt;Sydney&lt;/name&gt;
                &lt;country&gt;Australia&lt;/country&gt;
    &lt;/metro&gt;
...snip...
    &lt;metro&gt;
        &lt;name&gt;Wichita&lt;/name&gt;
                &lt;country&gt;United States&lt;/country&gt;
    &lt;/metro&gt;
&lt;/metros&gt;&lt;/lfm&gt; 
</code>
</pre>
    <h3>
      Implementing the geo.getMetros API call
    </h3>
    <p>
      Add the following code at the top of <code>lib/WWW/LastFM.pm</code> to load the WWW::LastFM::API::Geo module (which we'll flesh out in a moment).
    </p>
    <pre>
<code>use WWW::LastFM::API::Geo;
</code>
</pre>
    <p>
      Next we add another attribute so we can get easy access to the Geo class. Add at the bottom:
    </p>
    <pre>
<code># API modules
has 'geo' =&gt; (
    is         =&gt; 'ro',
    isa        =&gt; 'WWW::LastFM::API::Geo',
    lazy_build =&gt; 1,
);

sub _build_geo {
    my ($self) = @_;
    return WWW::LastFM::API::Geo-&gt;new( lastfm =&gt; $self );
}
</code>
</pre>
    <p>
      The next part is to create the basic skeleton that allows us to make API calls with convenience. Add the following code to <code>lib/WWW/LastFM/API/Geo.pm</code>:
    </p>
    <pre>
<code>package WWW::LastFM::API::Geo;
use Moose;
use namespace::autoclean;

use URI::Escape;

has 'lastfm' =&gt; (
    is       =&gt; 'ro',
    isa      =&gt; 'WWW::LastFM',
    required =&gt; 1,
);

# <a href="http://www.last.fm/api/show?service=435">http://www.last.fm/api/show?service=435</a>
# $country must be a binary encoded utf8 string

sub get_metros {
    my ($self, $country) = @_;
    return $self-&gt;lastfm-&gt;get(
           $self-&gt;lastfm-&gt;api_root_url
        . '?method=geo.getMetros'
        . ( $country ? '&amp;country=' . uri_escape($country) : "" )
        . '&amp;api_key=' . $self-&gt;lastfm-&gt;api_key
    );
}

__PACKAGE__-&gt;meta-&gt;make_immutable();

1;
</code>
</pre>
    <p>
      We perform another one-liner to verify that we're on the right track.
    </p>
    <pre>
<code>$ perl -Ilib -MWWW::LastFM -E 'say WWW::LastFM-&gt;new-&gt;geo-&gt;get_metros'
</code>
</pre>
    <p>
      What is interesting to see is that we've forwarded the instance of <code>WWW::LastFM</code> into the API class to avoid using global variables to hold our shared state. But let's see if we can't do something with that ugly XML.
    </p>
    <h3>
      Dealing with the root element in the response XML
    </h3>
    <p>
      Let's have a closer look at the XML returned.
    </p>
    <pre>
<code>&lt;?xml version="1.0" encoding="utf-8"?&gt;
&lt;lfm status="ok"&gt;
&lt;metros&gt;
    &lt;metro&gt;
        &lt;name&gt;Sydney&lt;/name&gt;
                &lt;country&gt;Australia&lt;/country&gt;
    &lt;/metro&gt;
... &lt;snip&gt; ...
    &lt;metro&gt;
        &lt;name&gt;Wichita&lt;/name&gt;
                &lt;country&gt;United States&lt;/country&gt;
    &lt;/metro&gt;
&lt;/metros&gt;&lt;/lfm&gt;
</code>
</pre>
    <p>
      What we can see is that Last.FM always wraps its response in a <code>&lt;lfm/&gt;</code> root element, as described in <a href="http://www.last.fm/api/rest">API REST requests</a>. It holds one attribute named <code>status</code>, and it's value is either <code>ok</code> or <code>failed</code>. So we're dealing with a boolean here. If the request failed it will contain an error code, like this:
    </p>
    <pre>
<code>&lt;?xml version="1.0" encoding="utf-8"?&gt;
&lt;lfm status="failed"&gt;
    &lt;error code="10"&gt;Invalid API Key&lt;/error&gt;
&lt;/lfm&gt;
</code>
</pre>
    <p>
      So let's see if we can encapsulate this behaviour in a class with a nice API. Let's first change our <code>get_metros</code> method to return this response class instead of some boring XML. Edit <code>lib/WWW/LastFM/API/Geo.pm</code> and add this after the other imports:
    </p>
    <pre>
<code>use WWW::LastFM::Response;
</code>
</pre>
    <p>
      And change the <code>get_metros</code> method into this:
    </p>
    <pre>
<code>sub get_metros {
    my ($self, $country) = @_;
    my $xml = $self-&gt;lastfm-&gt;get(
           $self-&gt;lastfm-&gt;api_root_url
        . '?method=geo.getMetros'
        . ( $country ? '&amp;country=' . uri_escape($country) : "" )
        . '&amp;api_key=' . $self-&gt;lastfm-&gt;api_key
    );
    return WWW::LastFM::Response-&gt;new( xml =&gt; $xml );
}
</code>
</pre>
    <p>
      So what should this WWW::LastFM::Response class look like? What we know is that it should require some XML to work on, and if it doesn't get that it should blow up with an error message (we want it to throw an exception of some kind). We also want to know if the response failed or not, and if it failed, we should throw an exception as well. If everything is okay, we should be able to get to those metros. Create <code>lib/WWW/LastFM/Response.pm</code> with this content:
    </p>
    <pre>
<code>package WWW::LastFM::Response;
use XML::Rabbit::Root 0.1.0;

sub BUILD {
    my ($self) = @_;
    return if $self-&gt;is_success;
    confess("Last.FM response error " . $self-&gt;error_code . ": " . $self-&gt;error);
}

has 'is_success' =&gt; (
    is         =&gt; 'ro',
    isa        =&gt; 'Bool',
    lazy_build =&gt; 1,
);

sub _build_is_success {
    my ($self) = @_;
    return unless $self-&gt;status eq 'ok';
    return 1;
}

has_xpath_value 'status'     =&gt; '/lfm/@status';
has_xpath_value 'error'      =&gt; '/lfm/error';
has_xpath_value 'error_code' =&gt; '/lfm/error/@code';

has_xpath_object 'metros' =&gt; '/lfm/metros' =&gt; 'WWW::LastFM::Response::MetroList';

finalize_class();
</code>
</pre>
    <p>
      You can test it immediately to see if it works as expected:
    </p>
    <pre>
<code>$ perl -Ilib -MWWW::LastFM -E 'say WWW::LastFM-&gt;new-&gt;geo-&gt;get_metros-&gt;status'
</code>
</pre>
    <p>
      You should get a string that says <code>ok</code> (unless you get an error because the Last.FM API server refuses to answer). If you want to force an error condition, try it out with an invalid API key.
    </p>
    <pre>
<code>$ perl -Ilib -MWWW::LastFM -E 'say WWW::LastFM-&gt;new( api_key =&gt; "1234" )-&gt;geo-&gt;get_metros-&gt;status'
Last.FM response error 10: Invalid API key - You must be granted a valid key by last.fm at ...
</code>
</pre>
    <p>
      I snipped the rest of the stack trace, as it is of no interest at this point. We know where we made a mistake.
    </p>
    <h3>
      How XML::Rabbit::Root simplifies dealing with the XML document data
    </h3>
    <p>
      Let's get into detail on how that class definition works.
    </p>
    <p>
      Using XML::Rabbit::Root does a whole lot of things behind the scenes. It is the equivalent of doing this:
    </p>
    <pre>
<code>use Moose;
with "XML::Rabbit::RootNode";
use namespace::autoclean;
use XML::Rabbit::Sugar;
</code>
</pre>
    <p>
      The <code>finalize_class()</code> at the bottom is the equivalent of <code>__PACKAGE__-&gt;meta-&gt;make_immutable(); 1;</code>, which ensures the class executes as fast as possible during runtime and that the file loaded returns a true value, as required by the perl parser. So with that out of the way, let's take a look at the meat of the class and what that imported sugar, <code>has_xpath_value</code> and <code>has_xpath_object</code>, represents.
    </p>
    <p>
      The first thing to notice is that there is no <code>xml</code> attribute declared in the class. This parameter comes from <a href="https://metacpan.org/module/XML::Rabbit::Role::Document">XML::Rabbit::Role::Document</a>. It is automatically available, and you must specify either <code>file</code>, <code>fh</code>, <code>xml</code> or <code>dom</code>, based on what kind of format your XML document is in. As we have it in a string, we use the <code>xml</code> parameter.
    </p>
    <p>
      The <code>BUILD</code> method is a special method Moose will automatically call after it has constructed an instance. In our case, we use it to verify the state of our instance. If the attribute <code>is_success</code> is true we just return, as everything is okay. If it is not, we throw an exception with the error code and text from the XML. This is how we can easily make an unwanted value in an attribute cause the entire construction of the object to fail.
    </p>
    <p>
      The <code>is_success</code> attribute is pretty straight-forward. We take a string value that represents the status and checks if it is negative (that is, NOT the value <code>ok</code>). If that is the case, we return false, otherwise things must be okay and we can return a true value. Notice that I use a <a href="http://c2.com/cgi/wiki?GuardClause">guard clause</a> to fail early. This is a best practice to avoid the dreaded <a href="http://c2.com/cgi/wiki?ArrowAntiPattern">arrow anti-pattern</a>.
    </p>
    <p>
      So what does the <code>has_xpath_value 'status' =&gt; '//lfm/@status';</code> declaration actually mean? Let's have a look in <a href="https://metacpan.org/module/XML::Rabbit::Sugar">XML::Rabbit::Sugar</a> and see if we can't figure it out. It says:
    </p>
    <blockquote>
      <pre>
<code>has_xpath_value($attr_name, $xpath_query, @moose_params)
</code>
</pre>
      <p>
        Extracts a single string according to the xpath query specified. The attribute <code>isa</code> parameter is automatically set to <code>Str</code>. The attribute native trait is automatically set to <code>String</code>.
      </p>
    </blockquote>
    <p>
      Okay, so this code creates a Moose attribute with an <code>isa</code> parameter of <code>Str</code> that will actually represent the value of the <code>status</code> attribute on the <code>lfm</code> root element in the XML document. Take a look at an <a href="http://zvon.org/comp/r/tut-XPath_1.html#Pages~List_of_XPaths">XPath tutorial</a> if you're unfamiliar with the syntax. If you need some additional Moose attribute parameters, you can specify them at the end. In our case there is no need for any. The <code>error</code> and <code>error_code</code> attribute is just the same thing.
    </p>
    <p>
      So what is this <code>has_xpath_object</code> thing? It represents exactly the same thing as <code>has_xpath_value</code>, but instead of being a string value, it is an object of the specified class. The <code>metros</code> attribute will represent the list of metros returned in the XML document. I was thinking about naming the attribute <code>metro_list</code>, to avoid nouns in plural, but decided to keep it in plural because it more closely matches the XML document layout. To avoid putting too much responsibility inside WWW::LastFM::Response, I've delegated dealing with this list of metros to another class. This follows object orientation best practices, which states that each class should have a clear and defined purpose.
    </p>
    <p>
      In the next article we'll dive into the details on how this <code>MetroList</code> class is implemented.
    </p><div class="item_footer"><p><small><a href="http://blog.robin.smidsrod.no/2011/10/01/implementing-www-lastfm-part-2">Original post</a> blogged on <a href="http://b2evolution.net/">b2evolution</a>.</small></p></div>]]></content:encoded>
								<comments>http://blog.robin.smidsrod.no/2011/10/01/implementing-www-lastfm-part-2#comments</comments>
			<wfw:commentRss>http://blog.robin.smidsrod.no/?tempskin=_rss2&#38;disp=comments&#38;p=99</wfw:commentRss>
		</item>
				<item>
			<title>Implementing WWW::LastFM, a client library to the Last.FM API, with XML::Rabbit</title>
			<link>http://blog.robin.smidsrod.no/2011/09/30/implementing-www-lastfm-part-1</link>
			<pubDate>Fri, 30 Sep 2011 17:24:00 +0000</pubDate>			<dc:creator>Robin Smidsr&#248;d</dc:creator>
			<category domain="main">Perl</category>
<category domain="alt">Software Development</category>			<guid isPermaLink="false">98@http://blog.robin.smidsrod.no/</guid>
						<description>&lt;ul&gt;
      &lt;li&gt;&lt;strong&gt;Part 1 of 5&lt;/strong&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;/2011/10/01/implementing-www-lastfm-part-2&quot;&gt;Part 2 of 5&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;/2011/10/02/implementing-www-lastfm-part-3&quot;&gt;Part 3 of 5&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;/2011/10/03/implementing-www-lastfm-part-4&quot;&gt;Part 4 of 5&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href=&quot;/2011/10/04/implementing-www-lastfm-part-5&quot;&gt;Part 5 of 5&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
    &lt;p&gt;
      In this series of articles I&#039;m going to implement a client to the &lt;a href=&quot;http://last.fm/&quot;&gt;Last.FM&lt;/a&gt; web services API which allows us to find concerts and other events in your local area. We&#039;ll use a CPAN module I&#039;ve created called &lt;a href=&quot;https://metacpan.org/module/XML::Rabbit&quot;&gt;XML::Rabbit&lt;/a&gt; to deal with all the mundane details of XML document handling.
    &lt;/p&gt;
    &lt;p&gt;
      Most of you probably don&#039;t like XML a lot. JSON is the new kid on the block, and dealing with all the crummy details of XML encoding, parsing and such is boring. Writing long incantations of XML::LibXML code to extract the required data is just something you would prefer not to do (unless you get paid for it, maybe not even then). I&#039;m going to show you a way to deal with XML that requires a lot less boilerplate code than you&#039;re most likely used to. Hopefully it will make dealing with XML-based APIs a lot more fun for you. In the process of showing you how to deal with XML with ease I&#039;ll also implement a simple, but extensible, framework for communicating with the Last.FM API. You can use the same framework design to build client libraries against other HTTP-based APIs. My hope is that the code I show you will inspire you to work with me on this particular Last.FM API or create clients for other interesting APIs.
    &lt;/p&gt;
    &lt;p&gt;
      I have done my best to follow good Perl programming practices, as advocated by chromatic&#039;s &lt;a href=&quot;http://www.onyxneon.com/books/modern_perl/&quot;&gt;Modern Perl book&lt;/a&gt;, Damian Conway&#039;s &lt;a href=&quot;http://shop.oreilly.com/product/9780596001735.do&quot;&gt;Perl Best Practices book&lt;/a&gt;, and the &lt;a href=&quot;https://metacpan.org/module/Moose::Manual&quot;&gt;Moose Manual&lt;/a&gt;. I&#039;ve also separated as much responsibility as possible into separate classes, attributes and methods. This should make it easier to create good tests that require a minimum amount of mocking to test both positive and negative failure scenarios.
    &lt;/p&gt;
    &lt;h3&gt;
      Getting a Last.FM API key and secret
    &lt;/h3&gt;
    &lt;p&gt;
      If you want to follow along, you&#039;ll need to get an API key from &lt;a href=&quot;http://www.last.fm/api/account&quot;&gt;http://www.last.fm/api/account&lt;/a&gt;. Just fill out the required fields with something useful to your person and get your API key and secret. When you have that, stick it in a file in your home directory called &lt;code&gt;.lastfm.ini&lt;/code&gt;.
    &lt;/p&gt;
    &lt;p&gt;
      The contents should be something like this:
    &lt;/p&gt;
    &lt;pre&gt;
&lt;code&gt;[API]
key = 012345678948bb4c75ff9608aac4fe83
secret = abcdef57349f6ad7e9959a63aa472
&lt;/code&gt;
&lt;/pre&gt;
    &lt;p&gt;
      That should ensure that the configurable information is tucked away in a personal file outside any code repository.
    &lt;/p&gt;
    &lt;p&gt;
      On Windows the correct directory will probably be &lt;code&gt;C:\Users\&amp;lt;username&amp;gt;\AppData\Local&lt;/code&gt;. Run the following command to figure out the correct location:
    &lt;/p&gt;
    &lt;pre&gt;
&lt;code&gt;$ perl -MFile::HomeDir -E &quot;say File::HomeDir-&amp;gt;my_data&quot;;
&lt;/code&gt;
&lt;/pre&gt;
    &lt;p&gt;
      If you don&#039;t feel like registering, you can actually use the API key used in the examples on the Last.FM API page, currently &lt;code&gt;b25b959554ed76058ac220b7b2e0a026&lt;/code&gt;. I&#039;m not sure how long it will work, but as we&#039;re not going to touch any of the authenticated API calls you won&#039;t need the API secret quite yet.
    &lt;/p&gt;
    &lt;h3&gt;
      Setting up your environment
    &lt;/h3&gt;
    &lt;p&gt;
      If you want to follow along without typing in all the code, look at the &lt;a href=&quot;https://github.com/robinsmidsrod/WWW-LastFM&quot;&gt;WWW-LastFM github repository&lt;/a&gt;. The project uses &lt;a href=&quot;https://metacpan.org/module/Dist::Zilla&quot;&gt;Dist::Zilla&lt;/a&gt;, so installing all the dependencies should be as easy as running the following commands:
    &lt;/p&gt;
    &lt;pre&gt;
&lt;code&gt;$ cpan Dist::Zilla
$ git clone git://github.com/robinsmidsrod/WWW-LastFM.git
$ cd WWW-LastFM
$ dzil authordeps | xargs cpan # or dzil authordeps | cpanm
$ dzil listdeps | xargs cpan   # or dzil listdeps | cpanm
&lt;/code&gt;
&lt;/pre&gt;
    &lt;h3&gt;
      The entry point class
    &lt;/h3&gt;
    &lt;p&gt;
      Okay, now we&#039;re ready to dive in!
    &lt;/p&gt;
    &lt;p&gt;
      Let&#039;s create some basic code to read that config file and get access to our API key and secret. Create the file &lt;code&gt;lib/WWW/LastFM.pm&lt;/code&gt; with this content:
    &lt;/p&gt;
    &lt;pre&gt;
&lt;code&gt;package WWW::LastFM;
use Moose;
use namespace::autoclean;

use File::HomeDir;
use Path::Class::Dir;
use Config::Any;

# Sometimes we like some extra debugging output
has &#039;debug&#039; =&amp;gt; (
    is      =&amp;gt; &#039;ro&#039;,
    isa     =&amp;gt; &#039;Bool&#039;,
    default =&amp;gt; 1,
);

# Standard stuff to read our config file
has &#039;config_file&#039; =&amp;gt; (
    is         =&amp;gt; &#039;ro&#039;,
    isa        =&amp;gt; &#039;Path::Class::File&#039;,
    lazy_build =&amp;gt; 1,
);

sub _build_config_file {
    my ($self) = @_;
    my $home = File::HomeDir-&amp;gt;my_data;
    my $conf_file = Path::Class::Dir-&amp;gt;new($home)-&amp;gt;file(&#039;.lastfm.ini&#039;);
    return $conf_file;
}

# This is where our config file data ends up
has &#039;config&#039; =&amp;gt; (
    is         =&amp;gt; &#039;ro&#039;,
    isa        =&amp;gt; &#039;HashRef&#039;,
    lazy_build =&amp;gt; 1,
);

sub _build_config {
    my ($self) = @_;
    my $cfg = Config::Any-&amp;gt;load_files({
        use_ext =&amp;gt; 1,
        files   =&amp;gt; [ $self-&amp;gt;config_file ],
    });
    foreach my $config_entry ( @{ $cfg } ) {
        my ($filename, $config) = %{ $config_entry };
        warn(&quot;Loaded config from file: $filename\n&quot;) if $self-&amp;gt;debug;
        return $config;
    }
    return {};
}

# And here we have our api key and secret
has &#039;api_key&#039; =&amp;gt; (
    is         =&amp;gt; &#039;ro&#039;,
    isa        =&amp;gt; &#039;Str&#039;,
    lazy_build =&amp;gt; 1,
);

sub _build_api_key { return (shift)-&amp;gt;config-&amp;gt;{&#039;API&#039;}-&amp;gt;{&#039;key&#039;}; }

has &#039;api_secret&#039; =&amp;gt; (
    is         =&amp;gt; &#039;ro&#039;,
    isa        =&amp;gt; &#039;Str&#039;,
    lazy_build =&amp;gt; 1,
);

sub _build_api_secret { return (shift)-&amp;gt;config-&amp;gt;{&#039;API&#039;}-&amp;gt;{&#039;secret&#039;}; }

__PACKAGE__-&amp;gt;meta-&amp;gt;make_immutable();

1;
&lt;/code&gt;
&lt;/pre&gt;
    &lt;p&gt;
      At this point you should be able to do read your API key from your config file with this small one-liner.
    &lt;/p&gt;
    &lt;pre&gt;
&lt;code&gt;$ perl -Ilib -MWWW::LastFM -E &#039;say WWW::LastFM-&amp;gt;new-&amp;gt;api_key&#039;
&lt;/code&gt;
&lt;/pre&gt;
    &lt;p&gt;
      This code is not really anything special. It&#039;s just a bunch of best-of-breed modules used to read an INI file in a dynamic directory based on your platform and putting its contents into a hash reference. Notice the use of lazy Moose attributes to transform your data from a filename, &lt;code&gt;.lastfm.ini&lt;/code&gt;, to a specific value, &lt;code&gt;api_key&lt;/code&gt;, one step at a time. Also notice that we do not need to use strict and warnings, as Moose takes care of that for us.
    &lt;/p&gt;
    &lt;p&gt;
      The next thing we need is a way to make requests, so let&#039;s use the tried and true LWP::UserAgent. Add this at the top of the file after the other imports:
    &lt;/p&gt;
    &lt;pre&gt;
&lt;code&gt;use LWP::UserAgent;

our $VERSION = &quot;0.0.1&quot;;
&lt;/code&gt;
&lt;/pre&gt;
    &lt;p&gt;
      And then continue and add the &lt;code&gt;api_root_url&lt;/code&gt; and &lt;code&gt;ua&lt;/code&gt; attribute that contains our HTTP client.
    &lt;/p&gt;
    &lt;pre&gt;
&lt;code&gt;# All access to the Last.FM API starts with this URL
has &#039;api_root_url&#039; =&amp;gt; (
    is      =&amp;gt; &#039;ro&#039;,
    isa     =&amp;gt; &#039;Str&#039;,
    default =&amp;gt; &#039;http://ws.audioscrobbler.com/2.0/&#039;,
);

# And finally our HTTP client that we will use to make requests
has &#039;ua&#039; =&amp;gt; (
    is         =&amp;gt; &#039;ro&#039;,
    isa        =&amp;gt; &#039;LWP::UserAgent&#039;,
    lazy_build =&amp;gt; 1,
);

sub _build_ua {
    my ($self) = @_;
    return LWP::UserAgent-&amp;gt;new( agent =&amp;gt; &#039;WWW::LastFM/&#039; . $VERSION );
}

# A utility method for making requests, returns raw XML
# or throws exception if no content was generated
sub get {
    my ($self, $url) = @_;
    confess(&quot;No URL specified&quot;) unless $url;
    my $response = $self-&amp;gt;ua-&amp;gt;get($url);
    my $content = $response-&amp;gt;content;
    confess(&quot;HTTP error: &quot; . $response-&amp;gt;status_line) unless defined $content;
    confess(&quot;HTTP error: &quot; . $response-&amp;gt;status_line) if $response-&amp;gt;code &amp;gt;= 500;
    return $content;
}
&lt;/code&gt;
&lt;/pre&gt;
    &lt;p&gt;
      We&#039;ve also added a basic &lt;code&gt;get&lt;/code&gt; method that fetches the data on the specified URL and returns the raw content (bytes). This should make it trivial to make API calls and get back XML we can work with.
    &lt;/p&gt;
    &lt;h3&gt;
      The geo.getMetros API call
    &lt;/h3&gt;
    &lt;p&gt;
      To test out that our HTTP client works, we can make another one-liner that fetches the locations (metros) the Last.FM service knows about.
    &lt;/p&gt;
    &lt;pre&gt;
&lt;code&gt;$ perl -Ilib -MWWW::LastFM -E &#039;my $lfm = WWW::LastFM-&amp;gt;new; say $lfm-&amp;gt;get($lfm-&amp;gt;api_root_url . &quot;?method=geo.getMetros&amp;amp;api_key=&quot; . $lfm-&amp;gt;api_key)&#039;
&lt;/code&gt;
&lt;/pre&gt;
    &lt;p&gt;
      You should get some XML spewed out on your screen that looks something like this:
    &lt;/p&gt;
    &lt;pre&gt;
&lt;code&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&amp;gt;
&amp;lt;lfm status=&quot;ok&quot;&amp;gt;
&amp;lt;metros&amp;gt;
    &amp;lt;metro&amp;gt;
        &amp;lt;name&amp;gt;Sydney&amp;lt;/name&amp;gt;
                &amp;lt;country&amp;gt;Australia&amp;lt;/country&amp;gt;
    &amp;lt;/metro&amp;gt;
...snip...
    &amp;lt;metro&amp;gt;
        &amp;lt;name&amp;gt;Wichita&amp;lt;/name&amp;gt;
                &amp;lt;country&amp;gt;United States&amp;lt;/country&amp;gt;
    &amp;lt;/metro&amp;gt;
&amp;lt;/metros&amp;gt;&amp;lt;/lfm&amp;gt;
&lt;/code&gt;
&lt;/pre&gt;
    &lt;p&gt;
      Let&#039;s see if we can&#039;t expose that information in a better way. If you have a closer look at &lt;a href=&quot;http://www.last.fm/api/intro&quot;&gt;Last.FM&#039;s API&lt;/a&gt; you&#039;ll notice that they divide their API calls into sub-sections. In the next part we&#039;ll make a separate class for the specific API calls we want to provide, in this case the &lt;code&gt;geo&lt;/code&gt; sub-section.
    &lt;/p&gt;&lt;div class=&quot;item_footer&quot;&gt;&lt;p&gt;&lt;small&gt;&lt;a href=&quot;http://blog.robin.smidsrod.no/2011/09/30/implementing-www-lastfm-part-1&quot;&gt;Original post&lt;/a&gt; blogged on &lt;a href=&quot;http://b2evolution.net/&quot;&gt;b2evolution&lt;/a&gt;.&lt;/small&gt;&lt;/p&gt;&lt;/div&gt;</description>
			<content:encoded><![CDATA[<ul>
      <li><strong>Part 1 of 5</strong></li>
      <li><a href="http://blog.robin.smidsrod.no/2011/10/01/implementing-www-lastfm-part-2">Part 2 of 5</a></li>
      <li><a href="http://blog.robin.smidsrod.no/2011/10/02/implementing-www-lastfm-part-3">Part 3 of 5</a></li>
      <li><a href="http://blog.robin.smidsrod.no/2011/10/03/implementing-www-lastfm-part-4">Part 4 of 5</a></li>
      <li><a href="http://blog.robin.smidsrod.no/2011/10/04/implementing-www-lastfm-part-5">Part 5 of 5</a></li>
    </ul>
    <p>
      In this series of articles I'm going to implement a client to the <a href="http://last.fm/">Last.FM</a> web services API which allows us to find concerts and other events in your local area. We'll use a CPAN module I've created called <a href="https://metacpan.org/module/XML::Rabbit">XML::Rabbit</a> to deal with all the mundane details of XML document handling.
    </p>
    <p>
      Most of you probably don't like XML a lot. JSON is the new kid on the block, and dealing with all the crummy details of XML encoding, parsing and such is boring. Writing long incantations of XML::LibXML code to extract the required data is just something you would prefer not to do (unless you get paid for it, maybe not even then). I'm going to show you a way to deal with XML that requires a lot less boilerplate code than you're most likely used to. Hopefully it will make dealing with XML-based APIs a lot more fun for you. In the process of showing you how to deal with XML with ease I'll also implement a simple, but extensible, framework for communicating with the Last.FM API. You can use the same framework design to build client libraries against other HTTP-based APIs. My hope is that the code I show you will inspire you to work with me on this particular Last.FM API or create clients for other interesting APIs.
    </p>
    <p>
      I have done my best to follow good Perl programming practices, as advocated by chromatic's <a href="http://www.onyxneon.com/books/modern_perl/">Modern Perl book</a>, Damian Conway's <a href="http://shop.oreilly.com/product/9780596001735.do">Perl Best Practices book</a>, and the <a href="https://metacpan.org/module/Moose::Manual">Moose Manual</a>. I've also separated as much responsibility as possible into separate classes, attributes and methods. This should make it easier to create good tests that require a minimum amount of mocking to test both positive and negative failure scenarios.
    </p>
    <h3>
      Getting a Last.FM API key and secret
    </h3>
    <p>
      If you want to follow along, you'll need to get an API key from <a href="http://www.last.fm/api/account">http://www.last.fm/api/account</a>. Just fill out the required fields with something useful to your person and get your API key and secret. When you have that, stick it in a file in your home directory called <code>.lastfm.ini</code>.
    </p>
    <p>
      The contents should be something like this:
    </p>
    <pre>
<code>[API]
key = 012345678948bb4c75ff9608aac4fe83
secret = abcdef57349f6ad7e9959a63aa472
</code>
</pre>
    <p>
      That should ensure that the configurable information is tucked away in a personal file outside any code repository.
    </p>
    <p>
      On Windows the correct directory will probably be <code>C:\Users\&lt;username&gt;\AppData\Local</code>. Run the following command to figure out the correct location:
    </p>
    <pre>
<code>$ perl -MFile::HomeDir -E "say File::HomeDir-&gt;my_data";
</code>
</pre>
    <p>
      If you don't feel like registering, you can actually use the API key used in the examples on the Last.FM API page, currently <code>b25b959554ed76058ac220b7b2e0a026</code>. I'm not sure how long it will work, but as we're not going to touch any of the authenticated API calls you won't need the API secret quite yet.
    </p>
    <h3>
      Setting up your environment
    </h3>
    <p>
      If you want to follow along without typing in all the code, look at the <a href="https://github.com/robinsmidsrod/WWW-LastFM">WWW-LastFM github repository</a>. The project uses <a href="https://metacpan.org/module/Dist::Zilla">Dist::Zilla</a>, so installing all the dependencies should be as easy as running the following commands:
    </p>
    <pre>
<code>$ cpan Dist::Zilla
$ git clone git://github.com/robinsmidsrod/WWW-LastFM.git
$ cd WWW-LastFM
$ dzil authordeps | xargs cpan # or dzil authordeps | cpanm
$ dzil listdeps | xargs cpan   # or dzil listdeps | cpanm
</code>
</pre>
    <h3>
      The entry point class
    </h3>
    <p>
      Okay, now we're ready to dive in!
    </p>
    <p>
      Let's create some basic code to read that config file and get access to our API key and secret. Create the file <code>lib/WWW/LastFM.pm</code> with this content:
    </p>
    <pre>
<code>package WWW::LastFM;
use Moose;
use namespace::autoclean;

use File::HomeDir;
use Path::Class::Dir;
use Config::Any;

# Sometimes we like some extra debugging output
has 'debug' =&gt; (
    is      =&gt; 'ro',
    isa     =&gt; 'Bool',
    default =&gt; 1,
);

# Standard stuff to read our config file
has 'config_file' =&gt; (
    is         =&gt; 'ro',
    isa        =&gt; 'Path::Class::File',
    lazy_build =&gt; 1,
);

sub _build_config_file {
    my ($self) = @_;
    my $home = File::HomeDir-&gt;my_data;
    my $conf_file = Path::Class::Dir-&gt;new($home)-&gt;file('.lastfm.ini');
    return $conf_file;
}

# This is where our config file data ends up
has 'config' =&gt; (
    is         =&gt; 'ro',
    isa        =&gt; 'HashRef',
    lazy_build =&gt; 1,
);

sub _build_config {
    my ($self) = @_;
    my $cfg = Config::Any-&gt;load_files({
        use_ext =&gt; 1,
        files   =&gt; [ $self-&gt;config_file ],
    });
    foreach my $config_entry ( @{ $cfg } ) {
        my ($filename, $config) = %{ $config_entry };
        warn("Loaded config from file: $filename\n") if $self-&gt;debug;
        return $config;
    }
    return {};
}

# And here we have our api key and secret
has 'api_key' =&gt; (
    is         =&gt; 'ro',
    isa        =&gt; 'Str',
    lazy_build =&gt; 1,
);

sub _build_api_key { return (shift)-&gt;config-&gt;{'API'}-&gt;{'key'}; }

has 'api_secret' =&gt; (
    is         =&gt; 'ro',
    isa        =&gt; 'Str',
    lazy_build =&gt; 1,
);

sub _build_api_secret { return (shift)-&gt;config-&gt;{'API'}-&gt;{'secret'}; }

__PACKAGE__-&gt;meta-&gt;make_immutable();

1;
</code>
</pre>
    <p>
      At this point you should be able to do read your API key from your config file with this small one-liner.
    </p>
    <pre>
<code>$ perl -Ilib -MWWW::LastFM -E 'say WWW::LastFM-&gt;new-&gt;api_key'
</code>
</pre>
    <p>
      This code is not really anything special. It's just a bunch of best-of-breed modules used to read an INI file in a dynamic directory based on your platform and putting its contents into a hash reference. Notice the use of lazy Moose attributes to transform your data from a filename, <code>.lastfm.ini</code>, to a specific value, <code>api_key</code>, one step at a time. Also notice that we do not need to use strict and warnings, as Moose takes care of that for us.
    </p>
    <p>
      The next thing we need is a way to make requests, so let's use the tried and true LWP::UserAgent. Add this at the top of the file after the other imports:
    </p>
    <pre>
<code>use LWP::UserAgent;

our $VERSION = "0.0.1";
</code>
</pre>
    <p>
      And then continue and add the <code>api_root_url</code> and <code>ua</code> attribute that contains our HTTP client.
    </p>
    <pre>
<code># All access to the Last.FM API starts with this URL
has 'api_root_url' =&gt; (
    is      =&gt; 'ro',
    isa     =&gt; 'Str',
    default =&gt; 'http://ws.audioscrobbler.com/2.0/',
);

# And finally our HTTP client that we will use to make requests
has 'ua' =&gt; (
    is         =&gt; 'ro',
    isa        =&gt; 'LWP::UserAgent',
    lazy_build =&gt; 1,
);

sub _build_ua {
    my ($self) = @_;
    return LWP::UserAgent-&gt;new( agent =&gt; 'WWW::LastFM/' . $VERSION );
}

# A utility method for making requests, returns raw XML
# or throws exception if no content was generated
sub get {
    my ($self, $url) = @_;
    confess("No URL specified") unless $url;
    my $response = $self-&gt;ua-&gt;get($url);
    my $content = $response-&gt;content;
    confess("HTTP error: " . $response-&gt;status_line) unless defined $content;
    confess("HTTP error: " . $response-&gt;status_line) if $response-&gt;code &gt;= 500;
    return $content;
}
</code>
</pre>
    <p>
      We've also added a basic <code>get</code> method that fetches the data on the specified URL and returns the raw content (bytes). This should make it trivial to make API calls and get back XML we can work with.
    </p>
    <h3>
      The geo.getMetros API call
    </h3>
    <p>
      To test out that our HTTP client works, we can make another one-liner that fetches the locations (metros) the Last.FM service knows about.
    </p>
    <pre>
<code>$ perl -Ilib -MWWW::LastFM -E 'my $lfm = WWW::LastFM-&gt;new; say $lfm-&gt;get($lfm-&gt;api_root_url . "?method=geo.getMetros&amp;api_key=" . $lfm-&gt;api_key)'
</code>
</pre>
    <p>
      You should get some XML spewed out on your screen that looks something like this:
    </p>
    <pre>
<code>&lt;?xml version="1.0" encoding="utf-8"?&gt;
&lt;lfm status="ok"&gt;
&lt;metros&gt;
    &lt;metro&gt;
        &lt;name&gt;Sydney&lt;/name&gt;
                &lt;country&gt;Australia&lt;/country&gt;
    &lt;/metro&gt;
...snip...
    &lt;metro&gt;
        &lt;name&gt;Wichita&lt;/name&gt;
                &lt;country&gt;United States&lt;/country&gt;
    &lt;/metro&gt;
&lt;/metros&gt;&lt;/lfm&gt;
</code>
</pre>
    <p>
      Let's see if we can't expose that information in a better way. If you have a closer look at <a href="http://www.last.fm/api/intro">Last.FM's API</a> you'll notice that they divide their API calls into sub-sections. In the next part we'll make a separate class for the specific API calls we want to provide, in this case the <code>geo</code> sub-section.
    </p><div class="item_footer"><p><small><a href="http://blog.robin.smidsrod.no/2011/09/30/implementing-www-lastfm-part-1">Original post</a> blogged on <a href="http://b2evolution.net/">b2evolution</a>.</small></p></div>]]></content:encoded>
								<comments>http://blog.robin.smidsrod.no/2011/09/30/implementing-www-lastfm-part-1#comments</comments>
			<wfw:commentRss>http://blog.robin.smidsrod.no/?tempskin=_rss2&#38;disp=comments&#38;p=98</wfw:commentRss>
		</item>
			</channel>
</rss>

