Catalyst::Plugin::Server::XMLRPC::Tutorial(3)

NAME
Catalyst::Plugin::Server::XMLRPC::Tutorial -- Using Catalyst as XMLRPC Server

Setting up your server
Please consult the Catalyst::Tutorial on how to set up your basic application, in which you can use this plugin.

  Configuring your server
Next, you can configure the application to do your bidding. First of all, every "XMLRPC" server needs to have an entrypoint. That is the url that every client needs to post to, in order to post methods to your server.

By default, this is "http://your.host.tld/rpc"

The full configuration options are described in Catalyst::Plugin::Server::XMLRPC.

Server types
You can set up the server in 2 ways; One is to merely dispatch to external code, the other is to integrate it in your Catalyst application.

Note that in both setups, the "usual" Catalyst dispatch logic is used, meaning that every method will have it's corresponding "begin", "auto" and "end" code called.

  Dispatching XMLRPC Server
This is the easiest configuration, that uses your catalyst app as just an XMLRPC server. This means that the XMLRPC server will just be used to dispatch to library code somewhere else.

Seting up the server

You could set this up as follows:

    package MyApp;
    use Catalyst    qw/Server Server::XMLRPC/;

    sub dispatcher : XMLRPCRegex('.') {
        ...
    }

Now, every xmlrpc call that gets posted to your application, will be handled by the "sub dispatcher", which will be shown in the server startup as follows:

    [catalyst] [debug] Loaded XMLRPCRegex actions:
    .---------------------------+----------------------------------.
    | XMLRPCRegex               | Private                          |
    +---------------------------+----------------------------------+
    | .                         | /dispatcher                      |
    '---------------------------+----------------------------------'

IMPORTANT: Newer versions of Catalyst add a forward to a default view when no response body has been defined yet, which interferes with this plugin. To fix this, look for the following line in your Root controller:

  sub end : ActionClass('RenderView') {}

And simply comment it out:

  #sub end : ActionClass('RenderView') {}

Handling incoming method calls

A user could now post to your XMLRPC server as follows, with the bundled rpc_client script in this distribution

    $ rpc_client -u http://your.host.tld/rpc -m foo

Your dispatcher now has to deal with the incoming request. The data returned, is whatever is present on the stash.

The below, contrived, example simply returns the method name that was called.

    sub dispatcher : XMLRPCRegex('.') {
        my ($self, $c, @args) = @_;
        $c->stash->{ method } = $c->request->xmlrpc->method;
    }

  Application XMLRPC Server
This uses your catalyst application as an XMLRPC server, dispatching method calls to your catalyst app, rather than external code. This also allows you to use the XMLRPC plugin transparently, meaning you can post to the same method in your class both via the web, and via XMLRPC.

Setting up the server

You could set this up as follows:

    package MyApp;
    use Catalyst    qw/Server Server::XMLRPC/;

    package MyApp::Controller::RPC;

    ### available as rpc.path
    sub goto_path : XMLRPCPath('/rpc/path') {
        my ($self, $c, @args) = @_;
        ...
    }

    ### available as any method containing 'foo'
    sub goto_regex : XMLRPCRegex('foo') {
        my ($self, $c, @args) = @_;
        ...
    }

    ### available as rpc.goto_local
    sub goto_local : XMLRPCLocal {
        my ($self, $c, @args) = @_;
        ...
    }

    ### avaiable as goto_global
    sub goto_global : XMLRPCGlobal {
        my ($self, $c, @args) = @_;
        ...
    }

This gives you several method calls, available as rpc calls, as also shown by the catalyst server startup messages:

    [catalyst] [debug] Loaded XMLRPCPath Method actions:
    .---------------------------+---------------------------------------.
    | XMLRPCPath Method         | Private                               |
    +---------------------------+---------------------------------------+
    | goto_global               | /rpc/goto_global                      |
    | rpc.goto_local            | /rpc/goto_local                       |
    | rpc.path                  | /rpc/goto_path                        |
    '---------------------------+---------------------------------------'

    [catalyst] [debug] Loaded XMLRPCRegex actions:
    .---------------------------+---------------------------------------.
    | XMLRPCRegex               | Private                               |
    +---------------------------+---------------------------------------+
    | foo                       | /rpc/goto_regex                       |
    '---------------------------+---------------------------------------'

A user could now post to your XMLRPC server as follows, with the bundled rpc_client script in this distribution, to get to the "goto_regex" method;

    $ rpc_client -u http://your.host.tld/rpc -m foo.bar

Arguments
Any XMLRPC call will have arguments available to it, if the client provided them.

To use our dispatcher exaple again:

    package MyApp;
    use Catalyst    qw/Server Server::XMLRPC/;

    sub dispatcher : XMLRPCRegex('.') {
        my($self, $c, @args) = @_;

        ### the XMLRPC arguments, as a list
        ### same as the ones provides in @args
        $aref = $c->req->xmlrpc->args;

        ### if the arguments provided were a list with 1 single item
        ### and that item was a hashref, they are added as ->params,
        ### just like in regular catalyst
        $href = $c->req->xmlrpc->params

        ...
    }

Return values
By default, this plugin returns all values that are on the stash. If you wish to have XMLRPC specific return values, put them in "$c->stash->{xmlrpc}" and that will be the only thing returned to the client.

For example, returning the whole stash:

    sub some_rpc_call : XMLRPCLocal {
        ...
        $c->stash->{foo} = 1;
        $c->stash->{bar} = 2;
    }

    ### will return:
    { foo => 1, bar => 2 }

For exmpale, returning xmlrpc specific values:

    sub some_rpc_call : XMLRPCLocal {
        ...
        $c->stash->{foo}    = 1;
        $c->stash->{xmlrpc} = { bar => 2 };
    }

    ### will return:
    { bar => 2 }

Authentication
If you wish to add authentication to your XMLRPC server, you can do this as follows:

    package MyApp;
    use Catalyst    qw/Server Server::XMLRPC/;

    ### Make sure we show errors to the outside world
    __PACKAGE__->config({
            'xmlrpc' => {
                    'show_errors' => 1,
                }
            );

    ### always called on any method dispatch
    sub auto {
        my( $self, $c ) = @_;

        ### someone from an illegal ip posting to us!
        unless( in_allowed_ips( $c->request->address ) ) {

            $c->error( "You are not allowed to use this server" );
            return 0;
        }

        return 1;
    }

You can use this same mechanism to do user/password based authentication by checking for arguments provided to the xmlrpc server. See the "Arguments" section above.

Transparent XMLRPC
There is actually nothing stopping you from making your methods available both via the web, as traditionally done by Catalyst, and via XMLRPC as well.

You could set this up as follows:

    package MyApp;
    use Catalyst    qw/Server Server::XMLRPC/;

    package MyApp::Controller::RPC;

    ### available as rpc.path as well as /web/path
    sub goto_path : Path ('/web/path') : XMLRPCPath('/rpc/path') {
        my ($self, $c, @args) = @_;

        if( $c->request->xmlrpc->is_xmlrpc_request ) {
            ### got called via xmlrpc
            ...
        } else {
            ...
        }
    }

This makes "goto_path" available as "http://your.host.tld/web/path" and "http://your.host.tld/rpc" with method name "rpc.path"

Examples

  Standard remote call
Here's a bit of code that uses the default configuration of this package to dispatch an rpc method to a subroutine in our Catalyst application.

    package MyApp::Controller::Demo;

    sub echo : XMLRPC {
        my ($self, $c) = @_
        @args = @{ $c->req->xmlrpc->args };

        $c->stash->{xmlrpc} = \@args;
    }

    ### meanwhile, in an rpc client far away
    $ rpc_client -u http://your-host.tld/rpc -m demo.echo test
    Output: test

Let's break it down line by line;

    package MyApp::Controller::Demo;

This is the package you are currently working in. The XMLRPC server will dispatch, by default, to something under your "MyApp::Controller" namespace.

    sub echo : XMLRPC {

You now have a method that's available via XMLRPC, thanks to the "XMLRPC" attribute.

        @args = @{ $c->req->xmlrpc->args };

        $c->stash->{xmlrpc} = \@args;

First, we will retrieve our arguments from the XMLRPC::Server object.

We then set $c->stash->{xmlrpc} to @args, in good echo server tradition.

    ### meanwhile, in an rpc client far away
    $ rpc_client -u http://your-host.tld/rpc -m demo.echo test
    -----------------Output-----------
    $VAR1 = 'test';

The rpc_client uses 2 configurable parts: the "/rpc" appended to the http hostname, which is called the "entry point" of the server. "/rpc" is the default (you can configure this to anything you want). The XMLRPC server will only listen to requests that are posted to the "entry point".

The second part is the method the client posts to. To reach the method "Echo" in your package, the client will have to use the method "demo.echo"; "demo" as that is the name of your package, and the "echo" bit as it is the name of your method.

The fictive rpc_client program then just prints out what was returned to it.

  More code examples
Browse through the test directory of this distribution, to see some examples of all of the above usage. The relevant modules can be found under "t/lib" of this distribution.

AUTHORS
Jos Boumans (kane@cpan.org)

Michiel Ootjers (michiel@cpan.org)

BUG REPORTS
Please submit all bugs regarding "Catalyst::Plugin::Server" to "bug-catalyst-plugin-server@rt.cpan.org"

LICENSE
This library is free software, you can redistribute it and/or modify it under the same terms as Perl itself.