Catalyst-Plugin-JSONRPC/lib/Catalyst/Plugin/JSONRPC.pm
package Catalyst::Plugin::JSONRPC;
use strict;
our $VERSION = '0.01';
use JSON ();
sub json_rpc {
my $c = shift;
my $attrs = @_ > 1 ? {@_} : $_[0];
my $body = $c->req->body;
my $content = do { local $/; <$body> };
my $req;
eval { $req = JSON::jsonToObj($content) };
if ($@ || !$req) {
$c->log->debug(qq/Invalid JSON-RPC request: "$@"/);
$c->res->content_type('text/javascript+json');
$c->res->body(JSON::objToJson({
result => undef,
error => 'Invalid request',
}));
return 0;
}
my $res = 0;
my $method = $attrs->{method} || $req->{method};
if ($method) {
my $class = $attrs->{class} || caller(0);
if (my $code = $class->can($method)) {
my $remote;
my $attrs = attributes::get($code) || [];
for my $attr (@$attrs) {
$remote++ if $attr eq 'Remote';
}
if ($remote) {
$class = $c->components->{$class} || $class;
my @args = @{ $c->req->args };
$c->req->args( $req->{params} );
my $name = ref $class || $class;
my $action = Catalyst::Action->new(
{
name => $method,
code => $code,
reverse => "-> $name->$method",
class => $name,
namespace => Catalyst::Utils::class2prefix(
$name, $c->config->{case_sensitive}
),
}
);
$c->state( $c->execute( $class, $action ) );
$res = $c->state;
$c->req->args( \@args );
}
else {
$c->log->debug(qq/Method "$method" has no Remote attribute/)
if $c->debug;
}
}
else {
$c->log->debug(qq/Couldn't find JSON-RPC method "$method"/)
if $c->debug;
}
}
$c->res->content_type('text/javascript+json');
$c->res->body(JSON::objToJson({
result => $res,
error => undef,
id => $req->{id},
}));
return $res;
}
1;
__END__
=head1 NAME
Catalyst::Plugin::JSONRPC - Dispatch JSON-RPC methods with Catalyst
=head1 SYNOPSIS
# include it in plugin list
use Catalyst qw/JSONRPC/;
# Public action to redispatch
sub entrypoint : Global {
my ( $self, $c ) = @_;
$c->json_rpc;
}
# Methods with Remote attribute in the same class
sub echo : Remote {
my ( $self, $c, @args ) = @_;
return join ' ', @args;
}
=head1 DESCRIPTION
Catalyst::Plugin::JSONRPC is a Catalyst plugin to add JSON-RPC methods
in your controller class. It uses a same mechanism that XMLRPC plugin
does and actually plays really nicely.
=head2 METHODS
=over 4
=item $c->json_rpc(%attrs)
Call this method from a controller action to set it up as a endpoint
for RPC methods in the same class.
Supported attributes:
=over 8
=item class
name of class to dispatch (defaults to current one)
=item method
method to dispatch to (overrides JSON-RPC method name)
=back
=back
=head2 REMOTE ACTION ATTRIBUTE
This module uses C<Remote> attribute, which indicates that the action
can be dispatched through RPC mechanisms. You can use this C<Remote>
attribute and integrate JSON-RPC and XML-RPC together, for example:
sub xmlrpc_endpoint : Regexp('^xml-rpc$') {
my($self, $c) = @_;
$c->xmlrpc;
}
sub jsonrpc_endpoint : Regexp('^json-rpc$') {
my($self, $c) = @_;
$c->json_rpc;
}
sub add : Remote {
my($self, $c, $a, $b) = @_;
return $a + $b;
}
Now C<add> RPC method can be called either as JSON-RPC or
XML-RPC.
=head1 AUTHOR & LICENSE
Six Apart, Ltd. E<lt>cpan@sixapart.comE<gt>
This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself.
=head1 THANKS
Thanks to Sebastian Riedel for his L<Catalyst::Plugin::XMLRPC>, from
which a lot of code is copied.
=head1 SEE ALSO
L<Catalyst::Plugin::XMLRPC>, C<JSON>, C<JSONRPC>
=cut