File Coverage

blib/lib/POE/Component/Server/JSONRPC.pm
Criterion Covered Total %
statement 15 59 25.4
branch 0 14 0.0
condition 0 10 0.0
subroutine 5 12 41.6
pod 7 7 100.0
total 27 102 26.4


line stmt bran cond sub pod time code
1             package POE::Component::Server::JSONRPC;
2 1     1   4 use strict;
  1         2  
  1         30  
3 1     1   5 use warnings;
  1         1  
  1         29  
4 1     1   4 use base qw/Class::Accessor::Fast/;
  1         2  
  1         859  
5              
6             our $VERSION = '0.05';
7              
8 1         9 use POE qw/
9             Filter::Line
10 1     1   5380 /;
  1         55185  
11 1     1   119925 use JSON;
  1         16677  
  1         5  
12              
13             =head1 NAME
14              
15             POE::Component::Server::JSONRPC - POE tcp or http based JSON-RPC server
16              
17             =head1 SYNOPSIS
18              
19             #http version:
20             POE::Component::Server::JSONRPC::Http->new(
21             Port => 3000,
22             Handler => {
23             'echo' => 'echo',
24             'sum' => 'sum',
25             },
26             SslKey => '/path/to/the/server.key',
27             SslCert => '/path/to/the/server.crt',
28             Authenticate => \&authentication_handler,
29             # authentication_handler must be a function that takes two parameters login and password,
30             # and returns true if connection is successful, false otherwise
31             );
32              
33             #tcp version:
34             POE::Component::Server::JSONRPC::Tcp->new(
35             Port => 3000,
36             Handler => {
37             'echo' => 'echo',
38             'sum' => 'sum',
39             },
40             );
41              
42             sub echo {
43             my ($kernel, $jsonrpc, $id, @params) = @_[KERNEL, ARG0..$#_ ];
44              
45             $kernel->post( $jsonrpc => 'result' => $id, @params );
46             }
47              
48             sub sum {
49             my ($kernel, $jsonrpc, $id, @params) = @_[KERNEL, ARG0..$#_ ];
50              
51             $kernel->post( $jsonrpc => 'result' => $id, $params[0] + $params[1] );
52             }
53              
54             =head1 DESCRIPTION
55              
56             This module is a POE component for tcp or http based JSON-RPC Server.
57              
58             The specification is defined on http://json-rpc.org/ and this module use JSON-RPC 1.0 spec (1.1 does not cover tcp streams)
59              
60             =head1 METHODS
61              
62             =head2 new
63              
64             Create JSONRPC component session and return the session id.
65              
66             Parameters:
67              
68             =over
69              
70             =item Port
71              
72             Port number for listen.
73              
74             =item Handler
75              
76             Hash variable contains handler name as key, handler poe state name as value.
77              
78             Handler name (key) is used as JSON-RPC method name.
79              
80             So if you send {"method":"echo"}, this module call the poe state named "echo".
81              
82             =back
83              
84             =cut
85              
86             sub new {
87 0 0   0 1   my $self = shift->SUPER::new( @_ > 1 ? {@_} : $_[0] );
88              
89 0           $self->{parent} = $poe_kernel->get_active_session->ID;
90 0   0       $self->{json} ||= JSON->new;
91              
92 0           my $session = POE::Session->create(
93             object_states => [
94             $self => {
95 0           map { ( $_ => "poe_$_", ) }
96             qw/_start init_server input_handler result error send/
97             },
98             ],
99             );
100              
101 0           $session->ID;
102             }
103              
104             =head1 HANDLER PARAMETERS
105              
106             =over
107              
108             =item ARG0
109              
110             A session id of PoCo::Server::JSONRPC itself.
111              
112             =item ARG1
113              
114             The id of the client you're treating, send that back in result/error.
115              
116             =item ARG2 .. ARGN
117              
118             JSONRPC argguments
119              
120             =back
121              
122             ex) If you send following request
123              
124             {"method":"echo", "params":["foo", "bar"]}
125              
126             then, "echo" handler is called and parameters is that ARG0 is component session id, ARG1 is client id, ARG2 "foo", ARG3 "bar".
127              
128             =head1 HANDLER RESPONSE
129              
130             You must call either "result" or "error" state in your handlers to response result or error.
131              
132             ex:
133              
134             $kernel->post( $component_session_id => "result" => $client_id, "result value" )
135              
136             $component_session_id is ARG0 in handler. If you do above, response is:
137              
138             {"result":"result value", "error":""}
139              
140              
141             =head1 POE METHODS
142              
143             Inner method for POE states.
144              
145             =head2 poe__start
146              
147             =cut
148              
149             sub poe__start {
150 0     0 1   my ($self, $kernel, $session, $heap) = @_[OBJECT, KERNEL, SESSION, HEAP];
151              
152 0           $heap->{clients} = {};
153 0           $heap->{id} = 0;
154              
155 0           $kernel->yield('init_server');
156             }
157              
158             =head2 poe_init_server
159              
160             Should be defined in Http or Tcp
161             =cut
162              
163 0     0 1   sub poe_init_server { print "error init_server\n"; }
164              
165             =head2 poe_input_handler
166              
167             =cut
168              
169             sub poe_input_handler {
170 0     0 1   my ($self, $kernel, $session, $heap, $request, $response, $dirmatch) = @_[OBJECT, KERNEL, SESSION, HEAP, ARG0..$#_ ];
171              
172 0           $heap->{clients}->{$heap->{id}} = {json_id => undef, response => $response};
173              
174 0           my $json;
175 0           eval {
176 0           $json = $self->{json}->decode( $request->content );
177             };
178 0 0         if ($@) {
179 0           $kernel->yield('error', $heap->{id}, q{invalid json request});
180 0           return;
181             }
182              
183 0           $heap->{clients}->{$heap->{id}} = {json_id => $json->{id}, response => $response};
184              
185 0 0 0       unless ($json and $json->{method}) {
186 0           $kernel->yield('error', $heap->{id}, q{parameter "method" is required});
187 0           return;
188             }
189              
190 0 0         unless ($self->{Handler}{ $json->{method} }) {
191 0           $kernel->yield('error', $heap->{id}, qq{no such method "$json->{method}"});
192 0           return;
193             }
194              
195 0           my $handler = $self->{Handler}{ $json->{method} };
196 0 0         my @params = @{ $json->{params} || [] };
  0            
197              
198 0           $kernel->post($self->{parent}, $handler, $session->ID, $heap->{id}, @params);
199              
200 0           $heap->{id}++;
201 0 0         if ($heap->{id}>=65535) { # limit to 2 bytes
202 0           $heap->{id} = 0;
203             }
204             }
205              
206             =head2 poe_result
207              
208             =cut
209              
210             sub poe_result {
211 0     0 1   my ($self, $kernel, $heap, $id, @results) = @_[OBJECT, KERNEL, HEAP, ARG0..$#_ ];
212              
213             #~ print "answering to ".$id."\n";
214              
215 0           my $client = $heap->{clients}->{$id};
216              
217 0 0 0       my $json_content = $self->{json}->encode(
218             { id => $client->{json_id} || undef,
219             error => undef,
220             result => (@results > 1 ? \@results : $results[0]),
221             }
222             );
223              
224             #~ print "json content : ".$json_content."\n";
225              
226 0           $kernel->yield('send',$client->{response},$json_content);
227 0           delete $heap->{clients}->{$id};
228             }
229              
230             =head2 poe_error
231              
232             =cut
233              
234             sub poe_error {
235 0     0 1   my ($self, $kernel, $heap, $id, $error) = @_[OBJECT, KERNEL, HEAP, ARG0..$#_];
236              
237 0           my $client = $heap->{clients}->{$id};
238              
239 0   0       my $json_error_content = $self->{json}->encode(
240             { id => $client->{json_id} || undef,
241             error => $error,
242             result => undef,
243             }
244             );
245              
246             #~ print "json content : ".$json_error_content."\n";
247              
248 0           $kernel->yield('send',$client->{response},$json_error_content);
249 0           delete $heap->{clients}->{$id};
250             }
251              
252             =head2 poe_send
253              
254             Should be defined in Http or Tcp
255             =cut
256              
257 0     0 1   sub poe_send { print "error poe_send\n"; }
258              
259             =head1 AUTHOR
260              
261             Daisuke Murase <typester@cpan.org>
262             Côme BERNIGAUD <come.bernigaud@laposte.net>
263              
264             =head1 COPYRIGHT
265              
266             This program is free software; you can redistribute
267             it and/or modify it under the same terms as Perl itself.
268              
269             The full text of the license can be found in the
270             LICENSE file included with this module.
271              
272             =cut
273              
274             1;