NAME
Froody::QuickStart - the froody Quick Start tutorial
DESCRIPTION
At the core of Froody is the concept of Froody Methods, methods that you can call remotely over the web.
For example, we have a hypothetical method called "examples.myapi.greet" that can return us greetings. We need to pass it one parameter, called "who", that is the name of the person we're greeting. These two things are passed as CGI parameters to a froody endpoint (a cgi script, a mod_perl server, whatever) sitting at "http://myserver.com/fe"
bash$ lwp-request 'http://myserver.com/fe?method=examples.myapi.greet&who=Mark'
<?xml version="1.0" encoding="utf-8"?>
<rsp stat="ok">
<greeting>Hello Mark!</greeting>
</rsp>
We get a chunk of XML back that is the result of calling the function.
Hello World Server Example
The easiest way to show you how Froody works is to expand the above example and demonstrate how we might implement that on the server.
Writing The API
Firstly we have to define a class that builds an API - a way of describing what methods can be called on our endpoint, along with what they expect to be passed and what they're going to return. This is normally done by subclassing Froody::API::XML and implementing xml
to return a block of XML that describes the method(s):
package MyAPI;
use base qw(Froody::API::XML);
1;
sub xml {
return <<"XML";
<spec>
<methods>
<!-- each method that we can call via the API needs one of these -->
<method name="examples.myapi.greet">
<!-- the parameters we pass to the method -->
<arguments>
<argument name="who" type="text" optional="0" />
</arguments>
<!-- an example response, what the returned XML will look like -->
<response>
<greeting>Hello World!</greeting>
</response>
</method>
</methods>
</spec>
XML
}
Note that I've omitted the optional documentation sections that should normally be included (the <errors>
and <description>
sections.)
Though this seems like a lot of stuff to have to write upfront, it saves us a lot of work in the Perl code we're going to write later since it allows Froody to verify parameters for you and convert your return values to XML automatically.
Information on exactly what should be put in an XML spec can be found by reading up on Froody::API::XML.
Writing The Implementation
Having declared our API we now need to write a class that implements it - i.e. write the code that actually does the work of calculating the greeting.
This is done like so:
package SayHello;
use base qw(Froody::Implementation);
# say what we implement.
sub implements { MyAPI => "examples.myapi.*" }
sub greet
{
my $self = shift;
my $args = shift;
return "Hello $args->{who}!";
}
Note that we don't have to do any checks to see if we've got the required parameter $args->{who}
and returning an error instead, or wrapping our arguments in the right sort of XML, or even worrying about encoding the output or escaping anything that is going to cause our XML. This is all done for us by Froody.
More infomation on how we marry the implementations to the APIs can be found by reading the docs for Froody::Implementation. The way the data structure your methods returns is converted to XML is documented in Froody::Invoker::Implementation
Testing the Code From The Command Line
When you install the Froody distribution, you get the froody client command line tool installed . This can be used to test the method call
bash$ froody -MSayHello examples.api.greet who "Mark"
<?xml version="1.0" encoding="utf-8"?>
<rsp stat="ok">
<greeting>Hello Mark!</greeting>
</rsp>
The -M
option tells froody
to load a module. If a Froody::Implementation has been loaded into memory then the client will automatically use that to make local froody calls against.
Setting up the Webserver
Local implementations are all very well and good, but the point is to make Froody methods avalible across the web. To do this we need to set up our endpoint so that it knows about the Froody Methods it's supposed to serve.
For performance reasons this should really be run from a mod_perl handler, which can be configured with a section in your httpd.conf like so:
PerlModule SayHello;
PerlModule Froody::Server;
<Location /fe>
SetHandler perl
PerlHandler Froody::Server->handler
</Location>
This is all documented in Froody::Server. If you need a little more control over your server setup (i.e. you need multiple Froody endpoints for any one Apache) then you should read Froody::Server.
For testing or an infrequently called script, we could run our endpoint as a CGI script:
#!/usr/bin/perl
use warnings;
use strict;
use Froody::Server;
Froody::Server->dispatch();
Either way, we can now make calls to our server:
bash$ lwp-request 'http://myserver.com/fe?method=example.myapi.greet&who=Mark'
<?xml version="1.0" encoding="utf-8"?>
<rsp stat="ok">
<greeting>Hello Mark!</greeting>
</rsp>
Using Froody as a Client
Froody is useful as a client as well as a server:
# create a client and then tell it where to get its methods from
my $client = Froody::Dispatch->new();
$client->add_endpoint("http://myserver.com/fe");
# call the method on the remote server
my $response = $client->dispatch(
method => "example.myapi.greet",
params => { who => "Mark" },
);
# print out the same XML as above
print $response->render;
There are several different response objects, and the as_*
methods can be used to turn them into one another. For example:
use Froody::Response::PerlDS;
my $perl_ds = $response->as_perlds;
use Data::Dumper;
print Dumper $perl_ds->content;
Prints:
$VAR1 = {
name => "greeting"
value => "Hello Mark!"
};
Mostly you want to use the Froody::Response::Terse response that returns the data in the same format as you passed out of the original Perl method back on the server:
use Froody::Response::Terse;
my $terse = $response->as_terse;
use Data::Dumper;
print $terse->content; # prints "Hello Mark!"
We've added a shortcut for this, so all you need to do is:
my $response = $client->call('example.myapi.greet', who => 'Mark');
which is equivalient to:
my $rsp = $client->dispatch(
method => "example.myapi.greet",
params => { who => "Mark" },
);
my $response = $rsp->as_terse->content;
The terse response makes use of Reflection. The 'add_endpoint()' method calls the server to ask it what methods it provides and what the responses look like. It then uses these specifications (like the one we declared at the top of this document) to do the conversion back to the expected response.
A More Complicated Example
Of course, there would be little point returning XML if all we were going to do was return simple strings. We can return much more complicated data structures:
package Corelist::API;
use base qw(Froody::API::XML);
1;
sub xml {
return <<'XML';
<spec>
<methods>
<method name="froody.demo.core">
<description>When modules were distributed with core Perl</description>
<arguments>
<argument name="modules" type="csv" optional="0" />
</arguments>
<response>
<core_list module_core_list_version="1.23">
<module name="Foo::Bar" first_in="5.7">
<distribution perl_version="5.7" module_version="1.2"/>
<distribution perl_version="5.8" module_version="1.2"/>
</module>
<module name="Foo::Baz" first_in="5.7">
<distribution perl_version="5.7" module_version="1.2"/>
<distribution perl_version="5.8" module_version="1.2"/>
</module>
<serveradmin>
<name>Mark Fowler</name>
<email>mark@twoshortplanks.com</email>
</serveradmin>
</core_list>
</response>
</method>
</methods>
</spec>
XML
}
We now have elements that are contained within elements, attributes, and things that appear multiple times. I'll explain why we had to do that later.
First note that we've defined the argument type as csv
. This means that it expects the data passed to it via the CGI parameter to be a comma seperated list. So if we call this method like so
http://server.com/fe?method=examples.perl.core&modules=Foo::Bar,Foo::Baz
The arguments (the second parameter) that's passed to the method we define will be:
{
modules => [ "Foo::Bar", "Foo::Baz" ]
}
If there's only one value in our URL:
http://server.com/fe?method=exaples.perl.core&modules=Foo::Bar
We'll still get a one item array:
{
modules => [ "Foo::Bar" ]
}
The data structure we want to return is not nearly as complicated as the XML we've defined above.
{
module_core_list_version => "1.31",
module => [
{
name => "Foo::Bar",
first_in => "5.7",
distrbution => [
{ perl_version => "5.7", module_version => "1.2" },
{ perl_version => "5.8", module_version => "1.2" },
],
},
{
name => "Foo::Baz",
first_in => "5.7",
distrbution => [
{ perl_version => "5.7", module_version => "1.2" },
{ perl_version => "5.8", module_version => "1.2" },
],
},
],
server_admin => {
name => "Mark Fowler",
email => 'mark@twoshortplanks.com'
}
}
Note how we haven't specified which hash keys above are attributes and which contain data to be put in elements - in fact you can't tell what's going to turn into an attribute and what's going to turn into a child element by looking at the data structure alone. Froody works out what the right thing to do is by comparing the hash keys with the attributes and element names defined in the example response.
Froody also works out if something can occur multiple times by examining the example response. If you want to return a list of similar items, for example, you have to specify it multiple (at least 2) times.
Here's the implementation that can actually create these data structures for us (you can find a fully working demo of this example in the examples
directory in the Froody distribution).
package Corelist::Implementation;
use base qw(Froody::Implementation);
use strict;
use warnings;
1;
sub implements { 'Corelist::API' => "froody.demo.core" }
use Module::CoreList;
sub core
{
my $self = shift; # not used
my $args = shift;
# insert the static server stuff
my $ds = {
server_admin => {
name => "Mark Fowler",
email => 'mark@twoshortplanks.com',
},
module_core_list_version => Module::CoreList->VERSION,
module => [],
};
# for each of the modules
foreach my $module (@{ $args->{modules} })
{
my $module_hash = { name => $module, distribution => [] };
# what was the first one?
if (my $first_in = Module::CoreList->first_release($module))
{ $module_hash->{first_in} = $first_in }
# build the distributions list
foreach my $dist (sort { $a <=> $b } keys %Module::CoreList::version)
{
if (exists $Module::CoreList::version{ $dist }{ $module }) {
my $version = $Module::CoreList::version{ $dist }{ $module };
$version = "undef" unless defined $version;
push @{ $module_hash->{distribution} }, {
perl_version => $dist,
module_version => $version,
};
}
}
push @{ $ds->{module} }, $module_hash;
}
return $ds;
}
And now we can test the code with the Froody client:
bash$ froody -MGetCore examples.perl.core modules Tie::File
<?xml version="1.0" encoding="utf-8"?>
<rsp stat="ok">
<core_list module_core_list_version="2.02">
<module first_in="5.007003" name="Tie::File">
<distribution perl_version="5.007003" module_version="0.17"/>
<distribution perl_version="5.008" module_version="0.93"/>
<distribution perl_version="5.008001" module_version="0.97"/>
<distribution perl_version="5.008002" module_version="0.97"/>
<distribution perl_version="5.008003" module_version="0.97"/>
<distribution perl_version="5.008004" module_version="0.97"/>
<distribution perl_version="5.008005" module_version="0.97"/>
<distribution perl_version="5.008006" module_version="0.97"/>
<distribution perl_version="5.008007" module_version="0.97"/>
<distribution perl_version="5.009" module_version="0.97"/>
<distribution perl_version="5.009001" module_version="0.97"/>
<distribution perl_version="5.009002" module_version="0.97"/>
</module>
</core_list>
</rsp>
BUGS
None known.
Please report any bugs you find via the CPAN RT system. http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Froody
AUTHOR
Copyright Fotango 2005. All rights reserved.
Please see the main Froody documentation for details of who has worked on this project.
This module is free software; you can redistribute it and/or modify it under the same terms as Perl itself.