view release on metacpan or search on metacpan
lib/Froody/API/Reflection.pm view on Meta::CPAN
<argument name="code" optional="0">The code of the error type whose information is being requested.</argument>
</arguments>
<response>
$ERROR_INFO
</response>
<errors>
<error code="froody.error.notfound.errortype" message="Error Type not Found"/>
</errors>
</method>
<method name="froody.reflection.getSpecification">
<description>Request the full public specification for a froody endpoint.</description>
<response>
<spec>
<methods>
$METHOD_INFO
$METHOD_INFO
</methods>
<errortypes>
$ERROR_INFO
$ERROR_INFO
</errortypes>
lib/Froody/Dispatch.pm view on Meta::CPAN
my $dispatcher = Froody::Dispatch->new();
my $response = $dispatcher->call( "foo.bar.baz",
fred => "wilma"
);
or, as a client:
$client = Froody::Dispatch->new;
# uses reflection to load methods from the server
$client->add_endpoint( "uri" );
# look mah, no arguments!
$rsp = $invoker->invoke($client, 'service.wibble');
# ok, take some arguments then.
$rsp = $invoker->invoke($client, 'service.devide', divisor => 1, dividend => 2);
# alternatively, args can be passed as a hashref:
$args = { devisor => 1, devidend => 2 };
$rsp = $invoker->invoke($client, 'service.devide', $args);
lib/Froody/Dispatch.pm view on Meta::CPAN
use Froody::Response::Terse;
use Froody::Repository;
use Froody::Response::Error;
use Froody::Invoker::Remote;
use Froody::Error qw(err);
use Froody::Logger;
my $logger = get_logger("froody.dispatch");
__PACKAGE__->mk_accessors(qw{response_class error_class endpoints filters});
=head1 METHODS
=head2 Class Methods
=over 4
=item new
Create a new instance of the dispatcher
=cut
sub new {
my $class = shift;
my $self = $class->SUPER::new(@_);
$self->endpoints({}) unless $self->endpoints;
$self->error_class('Froody::Response::Error') unless $self->error_class;
$self;
}
=item parse_cli (@args)
Parses a list of files, urls, modules, method filter expressions, and paths.
What you're able to do:
lib/Froody/Dispatch.pm view on Meta::CPAN
$self = $self->new; # we should be an instance.
}
my $repository = Froody::Repository->new;
$self->repository($repository);
for (@{ $args->{includes} }) {
unshift @INC, $_; #extend the search path
}
my @filters = @{ $args->{filters} || [] };
$self->filters(@filters);
for (@{ $args->{urls} || [] }) {
$self->add_endpoint( $_, @filters );
}
# warn Dumper($args->{modules}); use Data::Dumper;
# implementation has-a api_specification
# implementation has-a list of filters
my %api;
for my $module (@{ $args->{modules} || [] }) {
# make sure implementation is loaded for isa-checks etc.
lib/Froody/Dispatch.pm view on Meta::CPAN
}
=item add_implementation
Adds an implementation's methods to this dispatcher.
=cut
sub add_implementation {
my ($self, $module, @filters) = @_;
return if $self->endpoints->{$module}{loaded};
eval " use $module; 1" or die $@;
my ($api, $invoker);
my $repository = $self->repository;
$invoker = $self->endpoints->{$module}{invoker};
if ($module->isa('Froody::API')) {
$api = $module;
$invoker ||= (bless {}, "Froody::Invoker");
$self->repository->register_api( $api, $invoker, @filters );
} elsif ($module->isa("Froody::Implementation")) {
my @imp_filters;
$module->register_in_repository($repository, @filters);
}
$self->endpoints->{$module} = { invoker => $invoker, loaded => 1 };
}
=item cli_config
Parses arguments with L<parse_cli|/Froody::Dispatch>, and then calls
config with the arguments. This is intended to be used for parsing command
line options, and directly creating configuration details
Returns a dispatch object, and the parsed options.
lib/Froody/Dispatch.pm view on Meta::CPAN
sub default_repository
{
require Carp;
Carp::confess "Don't expect the default repo to introspect.";
}
=item call_via ($invoker, $method, [@ARGS, $args])
Calls C<$method> with C<$invoker>. If $invoker or $method are not
instances of C<Froody::Invoker> and C<Froody::Method> respectively then
this method will attempt to discover them in the registered list of endpoints
and the method repository.
Returns a Froody::Response object.
=cut
sub call_via {
my ($self, $invoker, $method ) = splice @_, 0, 3; #pull off first three args.
my $args = ref $_[0] eq 'HASH' ? $_[0]
: { @_ };
Carp::confess "You must provide an invoker" unless $invoker;
if (!UNIVERSAL::isa($invoker, 'Froody::Invoker')) {
$self->endpoints({}) unless $self->endpoints();
$invoker = $self->endpoints->{$invoker}{invoker}
}
$method = $self->get_method($method, $args)
unless UNIVERSAL::isa($method, 'Froody::Method');
my $meta = {
dispatcher => $self,
method => $method,
params => $args,
repository => $self->repository,
};
return $invoker->invoke($method, $args, $meta);
}
=item add_endpoint( "url" )
Registers all methods from a remote repository within this one.
TODO: add regex filtering of methods.
=cut
sub add_endpoint {
my ($self,$url, @method_filters) = @_;
die "add_endpoint requires an url" unless $url;
my $endpoints = $self->endpoints;
if ($endpoints->{$url}{loaded}) {
$logger->warn("Attempted to add endpoint($url) more than once.");
return $self;
}
$self->endpoints->{ $url }{invoker} ||= Froody::Invoker::Remote->new()->url($url);
$self->load_specification($url, @method_filters);
return $self;
}
=item load_specification ($name, @method_filters)
Load method and errortype specifications from a named endpoint
=cut
sub load_specification {
my ($self, $name, @method_filters) = @_;
my $endpoint = $self->endpoints->{$name};
my $invoker = $endpoint->{invoker};
my $repo = $self->repository;
my $response = $self->call_via($invoker, 'froody.reflection.getSpecification' );
if ($response->as_xml->status eq 'ok') {
my @structures = Froody::API::XML->load_spec($response->as_xml->xml);
$repo->load($invoker, \@structures, @method_filters);
$endpoint->{$endpoint}{loaded} = time;
}
}
=back
=head2 Instance Methods
=over
=item get_method( 'full_name', args )
lib/Froody/Dispatch.pm view on Meta::CPAN
my $repo = $self->repository;
my $method = eval { $repo->get_method($name, $args) };
return $method if $method;
# We didn't find it? Maybe it's been declared in our spec that may have
# changed since we last loaded it. Let's look for it in here!
# TODO: Improve this so it DTRT more (though works for now)
if (err('froody.invoke.nosuchmethod')) {
local $@ = $@;
my $get_info = $repo->get_method("froody.reflection.getMethodInfo", $args);
for (keys %{$self->endpoints || {}}) {
my $invoker = $self->endpoints->{$_}{invoker};
my $info = $self->call_via($invoker, $get_info, { %$args, method_name => $name })->as_xml;
if ($info->status eq 'ok') {
$method = Froody::API::XML->load_method( $info->xml->findnodes("/rsp/method") );
$method->invoker($invoker);
$repo->register_method($method);
}
return $method if $method;
}
}
lib/Froody/Invoker/Remote.pm view on Meta::CPAN
=head1 NAME
Froody::Invoker::Remote - invoker that calls methods remotely
=head1 SYNOPSIS
use Froody::Invoker::Remote;
my $remote = Froody::Invoker::Remote
->new()
->url("http://someserver.com/restendpoint/");
my $method = Froody::Method->new()
->full_name("fred.bar.baz")
->invoker($remote);
my $response = $method->call({ foo => "wibble" });
=head1 DESCRIPTION
An Invoker that calls a remote server to get the Froody::Response.
=head2 Accessors
=over
=item url
The URL of the REST endpoint.
=item source
Returns where this invoker originated
=back
=head1 BUGS
None known.
lib/Froody/QuickStart.pod view on Meta::CPAN
Froody::QuickStart - the froody Quick Start tutorial
=head1 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.
=head1 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.
=head2 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 C<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";
lib/Froody/QuickStart.pod view on Meta::CPAN
</rsp>
The C<-M> option tells C<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.
=head2 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 L<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 L<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:
lib/Froody/QuickStart.pod view on Meta::CPAN
<rsp stat="ok">
<greeting>Hello Mark!</greeting>
</rsp>
=head2 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;
lib/Froody/QuickStart.pod view on Meta::CPAN
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.
=head1 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;
lib/Froody/Server.pm view on Meta::CPAN
A Froody server. use as:
#!/usr/bin/perl
use warnings;
use strict;
use Froody::Server;
Froody::Server->dispatch();
..in a CGI script that is a Froody endpoint.
This server accepts a CGI request as the Froody request, and will dispatch
the method, and return the XML of the response as the result of the HTTP
request. If the dispatcher throws an error, we catch it and wrap it in
a L<Froody::Response> object that represents the error.
You can pass a namespace to dispatch into to the L<dispatch()> call, to
override the default L<Froody::Dispatch> namespace. This is strongly
recommended, or your code will be fairly useless.
lib/Froody/Server/Test.pm view on Meta::CPAN
my $server = Froody::Server::Standalone->new();
$server->config({
modules => \@impl,
});
$server->port($port);
$server->run;
}
return $child;
}
=item endpoint
Returns the endpoint for the Test server
=cut
sub endpoint {
my $class = shift;
return "http://localhost:$port/";
}
# start the web server
=item client
Returns a C<Froody::Dispatch> based client that points to the test
server. As a side-effect, it will start up the test server if it has
lib/Froody/Server/Test.pm view on Meta::CPAN
=cut
sub client {
my ($class, @impl) = @_;
$class->start(@impl);
sleep 1;
my $client = Froody::Dispatch->new();
$client->repository( Froody::Repository->new() );
$client->add_endpoint("http://localhost:$port");
return $client;
}
# stop is documented
sub stop {
if ($child) {
# what the gosh-darn signals numbered on this box then?
use Config;
defined $Config{sig_name} || die "No sigs?";
my ($i, %signo);
lib/Froody/SimpleClient.pm view on Meta::CPAN
=head1 SYNOPSIS
=head1 DESCRIPTION
=cut
package Froody::SimpleClient;
use warnings;
use strict;
use base qw( Class::Accessor::Chained::Fast );
__PACKAGE__->mk_accessors(qw( endpoint ));
use LWP::UserAgent;
use JSON::Syck;
use HTTP::Request::Common;
use Encode qw( encode_utf8 );
=head1 ATTRIBUTES
=over
=item endpoint
=back
=head1 METHODS
=over
=item new( endpoint, [ timeout => 30 ] )
We take an endpoint here. Optionally, we also take a timeout in seconds.
=cut
sub new {
my $class = shift;
my $endpoint = shift;
my $self = $class->SUPER::new({ endpoint => $endpoint });
$self->{ua} = LWP::UserAgent->new( @_ );
return $self;
}
=item call( method, arg => value, arg => value, etc... )
=cut
sub call {
my $self = shift;
lib/Froody/SimpleClient.pm view on Meta::CPAN
makes a raw call, returns the exact thing returned by the server.
=cut
sub call_raw {
my $self = shift;
my $method = shift;
my %args = ref $_[0] eq 'HASH' ? %{ $_[0] } : @_;
die "no endpoint set" unless $self->endpoint;
die "no UA" unless $self->{ua};
# fudge args so we can do the Right Thing with lists and uploads.
my $hash_expansion;
for (keys %args) {
my $value = $args{$_};
if (!defined $value) {
delete $args{$_};
next;
}
lib/Froody/SimpleClient.pm view on Meta::CPAN
die "can't handle type of argument '$_' (is a ".ref($args{$_}).")";
}
}
foreach (keys %args) {
unless (ref $args{$_}) {
$args{$_} = encode_utf8($args{$_});
}
}
# make the request
my $request = POST( $self->endpoint,
Content_Type => 'form-data',
Content => [ method => $method, %args ] );
my $response = $self->{ua}->request( $request );
Froody::Error->throw('froody.invoke.remote', 'Bad response from server: '. $response->status_line)
unless $response->is_success;
return $response->content;
}
t/froodydoc_pod_js.t view on Meta::CPAN
=head3 Errors
=over
=item froody.error.notfound.errortype - Error Type not Found
=back
=head2 froody.reflection.getSpecification
Request the full public specification for a froody endpoint.
=head3 Arguments
None.
=head3 Response
<response>
<spec>
t/froodydoc_wiky_js.t view on Meta::CPAN
<errortype code="mycode">Internal structure of your error type goes here (including XML)</errortype>
</response>
%]
== Errors ==
* *froody.error.notfound.errortype* - Error Type not Found
= froody.reflection.getSpecification =
Request the full public specification for a froody endpoint.
== Arguments ==
None.
== Response ==
[%
<response>
<spec>
t/high-level.t view on Meta::CPAN
use lib 't/lib';
use DTest;
use LWP::Simple ();
use Froody::Server::Test;
use Froody::SimpleClient;
ok( Froody::Server::Test->start( "DTest::Test" ), 'got interface to client');
ok( my $real_client = Froody::Server::Test->client, 'got interface to client');
like( LWP::Simple::get(Froody::Server::Test->endpoint . '?foo=1'),
qr/froody.invoke.nomethod/, 'get a froody error');
is $real_client->repository->get_methods, 14, "Right number of methods.";
eq_or_diff [ sort map { $_->full_name } $real_client->repository->get_errortypes],
[ '', 'perl.methodcall.param','test.error',],
"Right number of error types.";
sleep(1);
ok( my $simple_client = Froody::SimpleClient->new( Froody::Server::Test->endpoint,
timeout => 1 ), "got simpleclient" );
my $first = 1;
for my $client ( $real_client, $simple_client ) {
#for my $client ($simple_client ) {
my $answer;
lives_ok {
$client->call('froody.reflection.getErrorTypeInfo', code=> 'test.error');