blog.questright.com
http://blog.questright.com
From the right questions, the right answers.
-
Twitter deprecates “page” parameter
http://blog.questright.com/perl/7.html
Marc Mims
Copyright © 2009 Marc Mims
2009-09-25
<p>If you’re using the Perl Net::Twitter module to access the Twitter API’s
<code>friends</code>, <code>followers</code>, <code>friends_ids</code>, or <code>followers_ids</code> methods, you’ll need
to switch from using the <code>page</code> parameter to using the <code>cursor</code> parameter
before 26-Oct-2009.</p>
<p>Here’s how.</p>
<p>Suppose you have paging code, like this:</p>
<script src="http://gist.github.com/193644.js"></script>
<p>To use the <code>cursor</code> parameter:</p>
<script src="http://gist.github.com/193645.js"></script>
<p>Set the <code>cursor</code> parameter to an intial value of <code>-1</code>. Pass the <code>next_cursor</code>
value from the returned data structure on the next call. Repeat until
<code>next_cursor</code> is zero.</p>
<p>When the <code>cursor</code> parameter is used with the <code>friends</code> and <code>followers</code>
methods, the return value is a HashRef with keys <code>users</code>, <code>next_cursor</code>, and
<code>previous_cursor</code>. The value for <code>users</code> is an ArrayRef of users. When the
<code>cursor</code> parameter is excluded, the return value is just the ArrayRef of
users.</p>
<p>When the <code>cursor</code> parameter is used with the <code>friends_ids</code> and <code>followers_ids</code>
methods, the return value is a HashRef with keys <code>ids</code>, <code>next_cursor</code>, and
<code>previous_cursor</code>. The value for <code>ids</code> is an ArrayRef of user IDs. When the
<code>cursor</code> parameter is excluded, the return value is just the ArrayRef of user
IDs.</p>
<p>Twitter claims this new cursor based paging method is more reliable. It
returns slightly more complex data structure requiring some changes to your
code. It does, however, save one API call when getting all data. With the
<code>page</code> parameter, it was necessary to continue calling the API until an empty
array was returned. Using <code>cursor</code>, the <code>next_cursor</code> value is zero when the
final page is returned.</p>
<p><a href="http://groups.google.com/group/twitter-api-announce/t/52d4e68040d4ca45">Twitter plans to silently drop the <code>page</code> parameter</a> beginning
26-Oct-2009. So, update your code soon to avoid unexpected problems.</p>
-
Simulating module load failure
http://blog.questright.com/perl/6.html
Marc Mims
Copyright © 2009 Marc Mims
2009-07-03
<p>I did a bit of hacking on <a href="http://github.com/semifor/json-any">JSON::Any</a> today and added a new program to the
test suite in the process. Since I have all the backend JSON handlers
installed on my development machine (JSON, JSON::XS, JSON::DWIW, and the
deprecated JSON::Syck), I needed a way to ensure some tests are skipped when
one or more of those backends aren’t available.</p>
<p>A surprisingly simple solution came to mind:</p>
<pre><code>touch lib/JSON/DWIW.pm
</code></pre>
<p>That created an empty file in the project which of course doesn’t return the
requisite true value when <code>require</code>-ed. So, it simulated the need quite
nicely.</p>
<p>The sheer joy this simple hack gave me probably marks me a <a href="http://en.wikipedia.org/wiki/Geek">geek</a> more
surely the mouseless <a href="http://www.debian.org">Debian Linux</a>,
<a href="http://www.xmonad.org">Xmonad</a>-tiled-window-manager system I typed it on.</p>
<p>The only thing simpler was the fix required to make sure tests are skipped
when a backend can’t be loaded:</p>
<pre><code>- diag "$backend: $@" and skip("backend failed to load", 8) if $@;
+ diag("$backend: $@"), skip("backend failed to load", 8) if $@;
</code></pre>
<p>Apparently, <code>diag</code> doesn’t return true. (Should it?)</p>
<p>Hmmm…following <code>diag</code> through its call chain, we find our way to
<code>Test::Builder::_print_comment</code>, which explicitly calls <code>return 0</code>. I wonder
why?</p>
-
Irony: using FOSS to suppress freedom
http://blog.questright.com/perl/5.html
Marc Mims
Copyright © 2009 Marc Mims
2009-07-02
<p>Just out of curiosity, I used <a href="http://search.twitter.com/">Twitter search</a> to see how <a href="http://search.cpan.org/dist/Net-Twitter">Net::Twitter</a>
is being used.</p>
<p>I was dismayed to find <a href="http://search.twitter.com/search?q=net%3A%3Atwitter%20iranelection">warnings</a> to ignore posts from Perl Net::Twitter
with the <a href="http://search.twitter.com/search?q=%23iranelection">#iranelection</a> hashtag. Iranian government agents, they claim,
are creating <a href="http://twitter.com/iransource">spambots</a> with it.</p>
<blockquote>
<p><a href="http://twitter.com/CarlN">CarlN</a>: Govt agents here: Check signature of tweeters: posters from
“Perl Net::Twitter” are most certainly government spambots. RT RT
#iranelection <a href="http://twitter.com/CarlN/status/2249974434">11:24 PM Jun 19th</a> from web </p>
<p><a href="http://twitter.com/jimmy_45">jimmy_45</a>: STOP supporting US backed coup in Iran. #IranElection
#IranElection #tehran Tehran RT RT RT <a href="http://twitter.com/jimmy_45/status/2258151409">3:28 PM Jun 20th</a> from <a href="http://search.cpan.org/dist/Net-Twitter">Perl
Net::Twitter</a></p>
<p><a href="http://twitter.com/iransource">iransource</a>: 99% of tweets are from US backing a BLOODY
revolution. People are dying. STOP destabilizing the Iranian state!
#IranElection <a href="http://twitter.com/iransource/status/2259344624">5:25 PM Jun 20th</a> from <a href="http://search.cpan.org/dist/Net-Twitter">Perl Net::Twitter</a></p>
</blockquote>
<p>When you donate your time and effort to produce free, open source software,
you can’t control who uses it or how it’s used. The good guys and the bad
guys have equal access.</p>
<p>Here’s hoping the good guys win!</p>
-
RFC: Math::Round::Fair
http://blog.questright.com/perl/4.html
Marc Mims
Copyright © 2009 Marc Mims
2009-06-30
<p>I’ve developed a small module, Math::Round::Fair, which currently <a href="http://github.com/semifor/math-round-fair">resides on
github</a>. If the name is suitable and nothing like it already exists on
CPAN, I’ll upload it after some time for comment.</p>
<p>I <a href="http://blog.questright.com/programming/2.html">blogged about this a few years ago</a>, but until I needed the algorithm
again, recently, I didn’t attempt to create a CPAN module for it.</p>
<p>From the POD:</p>
<blockquote>
<p>This module provides a single, exportable function, “round_fair”, which
allocates an integer value, fairly distributing rounding errors.</p>
<p>Consider the problem of distributing one indivisible item, for example a penny,
across three evenly weighted accounts, A, B, and C.</p>
<p>Using a naive approach, none of the accounts will receive an allocation since
the allocated portion to each is 1/3 and 1/3 rounds to zero. We are left with
1 unallocated item.</p>
<p>Another approach is to adjust the basis at each step. We start with 1 item to
allocate to 3 accounts. 1/3 rounds to 0, so account A receives no allocation,
and we drop it from consideration. Now, we have 2 accounts and one item to
allocate. 1/2 rounds to 1, so we allocate 1 item to the account B. Account C
gets no allocation since there is nothing left to allocate.</p>
<p>But what happens if we allocate one item to the same three accounts 10,000
times? Ideally, two accounts should end up with 3,333 items and one should end
up with 3,334 items.</p>
<p>Using the naive approach, all three accounts receive no allocation since at
each round the allocation is 1/3 which rounds to zero. Using the second method,
account A and account C will received no allocation, and account B will receive
a total allocation of 10,000 items. Account B always receives the benefit of
the rounding errors.</p>
<p>“round_fair” uses an algorithm with randomness to ensure a fair distribution of
rounding errors. In our example problem, we start with 1 item to allocate. We
calculate account A’s share, 1/3. Since it is less than one item, we give it a
1/3 chance of rounding up (and, therefore, a 2/3 chance of rounding down). It
wins the allocation 1/3 of the time. 2/3 of the time we continue to B. We
calculate B’s allocation as 1/2 (since there are only 2 accounts remaining and
one item to allocate). B rounds up 1/2 of 2/3 (or 1/3) of the time and down
1/2 of 2/3 (or 1/3) of the time. If neither A nor B rounds up (which occurs
2/3 * 1/2, or 1/3 of the time), C’s allocation is calculated as 1/1 since we
have one item to allocate and only one account to allocate it to. So, 1/3 of
the time C receives the benefit of the rounding error. We never end up with
any unallocated items.</p>
<p>This algorithm works for any number of weighted allocations.</p>
</blockquote>
<p>The code is small enough to include here as well:</p>
<pre><code>sub round_fair {
my $value = shift;
croak "Value to be allocated must be an integer" unless int($value) == $value;
my $basis = 0;
for my $w ( @_ ) {
croak "Weights must be > 0" unless $w > 0;
$basis += $w;
}
return ($value) if @_ == 1;
map {
my $allocation = $value * $_ / $basis;
my $allocated = int $allocation;
my $remainder = $allocation - $allocated;
++$allocated if rand() < $remainder;
$basis -= $_;
$value -= $allocated;
$allocated
} @_;
}
</code></pre>
<p>Send comments to <a href="mailto:marc@questright.com">Marc Mims</a> or post them on
github.</p>
-
Transparent backwards compatibility using Moose
http://blog.questright.com/perl/3.html
Marc Mims
Copyright © 2009 Marc Mims
2009-06-30
<p>The current <a href="http://search.cpan.org/perldoc?Net::Twitter">Net::Twitter</a> is a complete rewrite of a <a href="http://search.cpan.org/~cthom/Net-Twitter-2.12/lib/Net/Twitter.pm">prior version</a>
using <a href="http://search.cpan.org/perldoc?Moose">Moose</a>. One of my design goals was transparent backwards
compatibility so that existing code based on Net::Twitter would continue to
run, unchanged (provided the additional required modules were installed).</p>
<p>By using <a href="http://search.cpan.org/perldoc?Moose::Role">Moose roles</a> I was able to factor out optional features and
legacy behavior into separate classes. The core functionality became
<a href="http://search.cpan.org/perldoc?Net::Twitter::Core">Net::Twitter::Core</a>. Net::Twitter itself became an <a href="http://en.wikipedia.org/wiki/Abstract_factory_pattern">object factory</a>
applying appropriate roles to create a concrete classes including the
appropriate mix of optional features then instances of those classes.</p>
<p>The <a href="http://search.cpan.org/perldoc?Net::Twitter::Role::Legacy">Legacy role</a> provides attributes and functionality that was not
carried forward into Net::Twitter::Core from Net::Twitter version 2.12. It
modifies the new core functionality with Moose method modifiers (in
particular, the <code>around</code> modifier). It also applies the set of other,
optional roles required to make Net::Twitter backwards compatible.</p>
<p>The interesting and challenging work was getting the Net::Twitter factory
class correct. Initially, I used <a href="http://search.cpan.org/perldoc?MooseX::Traits">MooseX::Traits</a> in Net::Twitter::Core
which provides the role application feature I needed with its
<code>new_with_traits</code> method. But simply having <code>Net::Twitter->new</code> call
<code>Net::Twitter::Core->new_with_traits</code> wasn’t sufficient for Net::Twitter
derived classes. I ended up stealing a bit of the code and the concepts from
MooseX::Traits to get the behavior I needed.</p>
<p>Net::Twitter’s only public method is <code>new</code>. It can be called with a <code>traits</code>
argument, or with a <code>legacy</code> argument which is just a shortcut to save a bit
of typing. Traits and roles are the same thing. I use the terms
interchangeably here.</p>
<pre><code># The following are all equivalent, creating objects
# backwards compatible with Net::Twitter 2.12:
my $nt = Net::Twitter->new;
my $nt = Net::Twitter->new(legacy => 1);
my $nt = Net::Twitter->new(traits => ['Legacy']);
# New can also create non-legacy variations
my $nt = Net::Twitter->new(traits => ['API::REST', 'OAuth']);
</code></pre>
<p>Here is the complete <code>new</code> method:</p>
<pre><code>sub new {
my $class = shift;
croak '"new" is not an instance method' if ref $class;
my %args = @_ == 1 && ref $_[0] eq 'HASH' ? %{$_[0]} : @_;
my $traits = delete $args{traits};
if ( defined (my $legacy = delete $args{legacy}) ) {
croak "Options 'legacy' and 'traits' are mutually exclusive. Use only one."
if $traits;
$traits = [ $legacy ? 'Legacy' : 'API::REST' ];
}
$traits ||= [ qw/Legacy/ ];
$traits = [ $class->$resolve_traits(@$traits) ];
my $superclasses = [ 'Net::Twitter::Core' ];
my $meta = $create_anon_class->($superclasses, $traits, 1);
# create a Net::Twitter::Core object with roles applied
my $new = $meta->name->new(%args);
# rebless it to include a subclass, if necessary
if ( $class ne __PACKAGE__ ) {
unshift @$superclasses, $class;
my $final_meta = $create_anon_class->($superclasses, $traits, 0);
bless $new, $final_meta->name;
}
return $new;
}
</code></pre>
<p>The <code>new</code> method expects either a single HASH reference or a list of key/value
pairs. It checks for and dereferences a HASH ref argument. Then it
normalizes the optional <code>legacy</code> argument to the appropriate traits. If no
<code>traits</code> or <code>legacy</code> argument is provided, it uses the default <code>Legacy</code> trait.</p>
<p>The first interesting bit of code is the call to <code>$create_anon_class</code>, which
may be called twice.</p>
<p>The first call to <code>$create_anon_class</code> creates a meta-class from
<code>Net::Twitter::Core</code> with the appropriate roles applied and assigns it to the
variable <code>$meta</code>. Anonymous classes aren’t /really/ anonymous; they have
auto-generated names. So, after creating the meta class, we create an
instance of the class it represents and assign the instance to the variable
<code>$new</code>.</p>
<p>If <code>Net::Twitter->new</code> was called directly, we’re done. We can simply return
the new instance. However, if <code>new</code> was called from some Net::Twitter derived
class we need to create another anonymous class with the derived class
prepended to the list of superclasses. It gets assigned to the variable
<code>$final_meta</code>.</p>
<p>We couldn’t create the first meta class with the derived class specified in
<code>superclasses</code>, because calling <code>new</code> on the class it represents is likely to
be infinitely recursive!</p>
<p>Consider this Net::Twitter derived class:</p>
<pre><code>package My::Net::Twitter::DerivedClass;
use base 'Net::Twitter';
sub some_new_method { ... }
</code></pre>
<p>Net::Twitter’s <code>new</code> method will be called with the <code>$class</code> argument
<code>My::Net::Twitter::DerivedClass</code>. Calling <code>new</code> on the resulting anonymous
class, with <code>My::Net::Twitter::DerivedClass</code> in <code>superclasses</code> will result in
an infinite recursion since Net::Twitter’s <code>new</code> will be called, again.</p>
<p>So, after creating an instance of the anonymous class without
<code>My::Net::Twitter::DerivedClass</code> in <code>superclasses</code>, we create another
anonymous class and assign it to the variable <code>$final_meta</code>. Then we rebless
our existing instance into the new anonymous class and return it. Reblessed
into the new class, it can find and use the derived class methods.</p>
<p><code>$create_anon_class</code> has some interesting features.</p>
<pre><code>my $create_anon_class = sub {
my ($superclasses, $traits, $immutable) = @_;
my $meta;
$meta = Net::Twitter::Core->meta->create_anon_class(
superclasses => $superclasses,
roles => $traits,
methods => { meta => sub { $meta }, isa => $isa },
cache => 1,
);
$meta->make_immutable(inline_constructor => $immutable);
return $meta;
};
</code></pre>
<p>With the <code>cache</code> argument set to <code>1</code>, <code>create_anon_class</code> caches the classes
it creates and returns an existing instance of the mata-class when the same
arguments are passed again. So, although <code>create_anon_class</code> may be called an
arbitrary number of times in the life of an application, a new, anonymous
class is only created when different roles or superclasses are specified.</p>
<p>A final point of interest is the method <code>isa</code> added to to the anonymous
classes.</p>
<pre><code>my $isa = sub {
my $self = shift;
my $isa = shift;
return $isa eq __PACKAGE__ || $self->SUPER::isa($isa)
};
</code></pre>
<p>Net::Twitter is an object factory. It does not create instances of itself.
It creates instances of anonymous classes based on Net::Twitter::Core with
roles applied. Classes derived from Net::Twitter may be surprised (and die!)
if <code>->isa('Net::Twitter')</code> fails on their instances. So, objects created by
Net::Twitter claim to be Net::Twitter instances when queried with <code>isa</code>.</p>
<p>Net::Twitter contains a bit more code than I’ve shown here. The additional
code, stolen from MooseX::Traits, simply expands role names, passed in the
<code>traits</code> argument into the Net::Twitter::Role namespace.</p>
<p>This may not be the best possible implementation, but it seems to work well
for all the conditions I have encountered tested. Your feed back is welcome.
You can send me email or find me online. I’m <code>semifor</code> on IRC and can usually
be found in the #moose and #net-twitter channels on irc.perl.org. I’m also
<code>semifor</code> on Twitter.</p>
-
Net::Twitter Roadmap
http://blog.questright.com/perl/2.html
Marc Mims
Copyright © 2009 Marc Mims
2009-06-12
<p>
Recently, I became the maintainer of
<a href="http://search.cpan.org/dist/Net-Twitter">Net::Twitter</a>.
</p>
<p>
Net::Twitter 3.01 is a complete rewrite using Moose. It uses Moose Roles
heavily to provide optional features, API support, and a choice of error
handling strategies. There is also a legacy role, Net::Twitter::Role::Legacy,
to provide transparent backwards compatibility with version 2.12. The legacy
role is applied by default, so aside from new dependencies, a CPAN upgrade
should be completely transparent to existing users.
</p>
<p>
Version 3.01 comes with some new features:
</p>
<ul>
<li> full coverage of the Twitter REST API, including the new saved_searches methods</li>
<li> OAuth support</li>
<li> optional, exception based error handling</li>
</ul>
<p>
And 3.02 will include an enhancement to the OAuth support for desktop
applications. (Twitter made a recent change for OAuth desktop apps requiring
the use of a PIN# to obtain access tokens.) A
<a href="http://search.cpan.org/~mmims/Net-Twitter-3.01000_01/">developer release</a>
was uploaded to CPAN today including that change. If no bugs are reported,
3.02 will roll out shortly.
</p>
<p>
A new feature under consideration is inflation of Twitter API responses to
Perl objects, rather than simple HASH refs. So, <code> $status->{user}->{screen_name} </code>
becomes <code> $status->user->screen_name </code>. And <code> status->created_at </code> may return a
DateTime object. Some feedback from the Net::Twitter users on this would be
appreciated.
</p>
<p>
My primary goal for the rewrite was ease of maintenance so that I could
respond quickly to the ever changing Twitter API specification. I
accomplished much of that goal with
<a href="http://search.cpan.org/dist/Net-Twitter/lib/Net/Twitter/API.pm">Net::Twitter::API</a>.
It extends Moose with some sugar so that Twitter API methods are described with a
declarative syntax.
</p>
<p>
The API methods themselves, declared in
<a href="http://search.cpan.org/dist/Net-Twitter/lib/Net/Twitter/Role/API/REST.pm">Net::Twitter::Role::API::REST</a>
and the other API::* modules, are dynamically generated. POD for the API
methods is also dynamically generated by introspecting the Moose meta data.
Even some of the tests are dynamically generated in the same way.
</p>
<p>
So, when Twitter adds, removes, or changes API methods, there should be little
work to do as a maintainer to get a new release prepared, tested, and
distributed.
</p>
<p>
There are many Net::Twitter 2.12 users who will be dismayed by the Moose
requirement. Not every one can install Moose and its dependencies, even if
they want to. Shared hosting systems, for instance, may not allow
installation of arbitrary modules. There are also Net::Twitter users who have
created plugins and distribute the module with their applications. They want
a module with limited dependencies.
</p>
<p>
<a href="http://search.cpan.org/dist/Net-Twitter-Lite">Net::Twitter::Lite</a> was created
to serve those users. It does not have all the features and flexibility
Net::Twitter does, but it is lean and has no more requirements than
Net::Twitter version 2.12. In addition, its API methods and documentation are
generated from the Net::Twitter 3.x source. So, an API change can be quickly
and reliably propagated to Net::Twitter::Lite.
</p>
<p>
For those who need Net::Twitter::Lite, the move from Net::Twitter 2.12 is
fairly painless, and instructions are included with the module.
</p>
<p>
Net::Twitter::Lite will get optional OAuth support, soon.
</p>
<p>
Transparent backwards compatibility is the default in Net::Twitter 3.01. That
may change in a future version, but only after a reasonable deprecation plan
is communicated to users.
</p>
<p>
Many thanks to <a href="http://search.cpan.org/~cthom">Chris Thompson</a> who authored the
original version of Net::Twitter and maintained it through version 2.12.
</p>
-
Fighting spam with spam
http://blog.questright.com/internet/19.html
Marc Mims
Copyright © 2009 Marc Mims
2009-06-09
<p>
I've always found it mildly irritating when I get a response to an email
message that says:
</p>
<blockquote>
<p>
I'm protecting myself from spam. Please click the link below to complete the
verification process. You have to do this only once.
</p>
</blockquote>
<p>
When I receive one of these messages in response to email I did NOT send, it's
not just irritating, it's infuriating.
</p>
<p>
It is a common technique of spammers to not only send spam to their harvested
addresses, but also to use them in forged <code>From</code> headers. With this type of
spam verification, you get to be a victim twice.
</p>
<p>
Spam filters have gotten quite good. I rarely received spam in my inbox. Any
spam filtering technique that generates unwanted mail itself should be
shunned. They are just adding to the problem.
</p>
<p>
The message I received came from <a href="http://www.spamarrest.com">Spam Arrest</a>. It
not only came with the verification, but an advertisement for the service.
Isn't that the very definition of spam?
</p>
<p>
I've added an email filter. All mail from spamarrest.com is automatically
reported as spam. If you happen to use Spam Arrest—sorry—your mail is
undeliverable here.
</p>
-
Testing with LWP::UserAgent
http://blog.questright.com/perl/1.html
Marc Mims
Copyright © 2009 Marc Mims
2009-06-06
<p>
<a href="http://search.cpan.org/perldoc?LWP::UserAgent">LWP::UserAgent</a> has a feature
that makes testing applications that use it very easy.
</p>
<p>
I wrote and maintain the
<a href="http://search.cpan.org/dist/Net-Twitter">Net::Twitter</a> and
<a href="http://search.cpan.org/dist/Net-Twitter-Lite">Net::Twitter::Lite</a>
distributions.<a href="#fnote1"><sup>1</sup></a> I needed a way to test API calls without actually
hitting the Twitter API servers.
</p>
<p>
The job required of the Net::Twitter modules is to turn simple Perl method
calls into HTTP requests and turn the HTTP responses back into useful Perl
data. It is, therefore sufficient to inspect an HTTP::Request object at the
point it would normally be transmitted to Twitter without actually sending it.
And, to return a suitable HTTP::Response object to test the applications
behavior.
</p>
<p>
It turns out, LWP::UserAgent has a callback mechanism that is perfect for this
task. The <code>add_handler</code> method takes a phase name, a code reference, and
optionally a matchspec to create callbacks at any of several phases. The
<code>request_send</code> phase occurs at exactly the right phase for testing: when the
HTTP::Request instance is fully configured and ready to send on the wire. If
the callback returns an HTTP::Response object, no network call is made. The
HTTP::Response provided by the callback is returned to the caller.
</p>
<p>
Here's an example test using this technique:
</p>
<pre><code>
1 use Test::More tests => 2;
2 use Net::Twitter::Lite;
3
4 my $nt = Net::Twitter::Lite->new;
5
6 my $request;
7 my %args;
8 my $response = HTTP::Response->new(200, 'OK');
9 $response->content('{"test":"success"}');
10
11 $nt->{ua}->add_handler(request_send => sub {
12 $request = shift;
13
14 $response->request($request);
15 %args = $request->uri->query_form;
16
17 return $response;
18 });
19
20 # additional args in a HASH ref
21 my $search_term = "intelligent life";
22 my $r = $nt->search($search_term, { page => 2 });
23 is $args{q}, $search_term, "q as positional arg";
24 is $args{page}, 2, "page parameter set";
</code></pre>
<p>
In lines 6-7 I declared some lexical variables that will be available in the
callback using a closure.
</p>
<p>
Line 11 sets up the callback on Net::Twitter::Lite's LWP::UserAgent instance.
</p>
<p>
The callback receives three parameters: the HTTP::Request instance, the
LWP::UserAgent instance, and a reference to the callback handler itself. Only
the HTTP::Request instance is of interest here. It is assigned to <code>$request</code>
on line 12.
</p>
<p>
An HTTP::Response includes a reference to its initiating HTTP::Request. Line 14
takes care of that.
</p>
<p>
Since this particular test is dealing with query parameters, they are
extracted from the request's URI on line 15 and stored in the HASH declared on
line 7.
</p>
<p>
On line 17, the HTTP::Response is returned to the caller. This prevents
LWP::UserAgent from actually making a network call.
</p>
<p>
Line 22 makes a Net::Twitter::Lite call that should result in an HTTP GET with
url <code><a href="http://search.twitter.com/search.json?page=2&q=intelligent+life">http://search.twitter.com/search.json?page=2&q=intelligent+life</a></code>.
Net::Twitter::Lite should inflate the HTTP::Response contents into an
appropriate Perl representation of the JSON return.
</p>
<p>
With the request and response available, a variety of tests can be run to
unsure Net::Twitter::Lite is behaving as expected.
</p>
<p>
You may find this technique useful for your own tests.
</p>
<p style="font-size: 72%;">
<a name="fnote1">[1]</a>Net::Twitter versions 2.12 and earlier were written and
maintained by Chris Thompson. Version 3 is a complete rewrite using Moose.
Net::Twitter::Lite was created for those who cannot or prefer not to install
Moose and its dependencies.
</p>