File Coverage

blib/lib/XAS/Lib/RPC/JSON/Server.pm
Criterion Covered Total %
statement 15 94 15.9
branch 0 20 0.0
condition n/a
subroutine 5 17 29.4
pod 4 4 100.0
total 24 135 17.7


line stmt bran cond sub pod time code
1             package XAS::Lib::RPC::JSON::Server;
2              
3             our $VERSION = '0.04';
4              
5 1     1   871 use POE;
  1         1  
  1         8  
6 1     1   278 use Try::Tiny;
  1         2  
  1         56  
7 1     1   3 use Set::Light;
  1         2  
  1         32  
8              
9             use XAS::Class
10 1         5 debug => 0,
11             version => $VERSION,
12             base => 'XAS::Lib::Net::Server',
13             utils => ':validation dotid',
14             accessors => 'methods',
15             codec => 'JSON',
16             constants => 'HASH ARRAY :jsonrpc ARRAYREF HASHREF',
17 1     1   3 ;
  1         2  
18              
19             my $ERRORS = {
20             '-32700' => 'Parse Error',
21             '-32600' => 'Invalid Request',
22             '-32601' => 'Method not Found',
23             '-32602' => 'Invalid Params',
24             '-32603' => 'Internal Error',
25             '-32099' => 'Server Error',
26             '-32001' => 'App Error',
27             };
28              
29 1     1   953 use Data::Dumper;
  1         2  
  1         887  
30              
31             # ----------------------------------------------------------------------
32             # Public Methods
33             # ----------------------------------------------------------------------
34              
35             sub process_request {
36 0     0 1   my $self = shift;
37 0           my ($input, $ctx) = validate_params(\@_, [
38             1,
39             { type => HASHREF }
40             ]);
41              
42 0           my $request;
43 0           my $alias = $self->alias;
44              
45 0           $self->log->debug("$alias: entering process_request");
46 0           $self->log->debug(Dumper($input));
47              
48             try {
49              
50 0     0     $request = decode($input);
51              
52 0 0         if (ref($request) eq ARRAY) {
53              
54 0           foreach my $r (@$request) {
55              
56 0           $self->_rpc_request($r, $ctx);
57              
58             }
59              
60             } else {
61              
62 0           $self->_rpc_request($request, $ctx);
63              
64             }
65              
66             } catch {
67              
68 0     0     my $ex = $_;
69              
70 0           $self->log->error(Dumper($input));
71 0           $self->exception_handler($ex);
72              
73 0           };
74              
75             }
76              
77             sub process_response {
78 0     0 1   my $self = shift;
79 0           my ($output, $ctx) = validate_params(\@_, [
80             1,
81             { type => HASHREF }
82             ]);
83              
84 0           my $json;
85 0           my $alias = $self->alias;
86              
87 0           $self->log->debug("$alias: entering process_response");
88              
89 0           $json = $self->_rpc_result($ctx->{'id'}, $output);
90              
91 0           $poe_kernel->post($alias, 'client_output', encode($json), $ctx);
92              
93             }
94              
95             sub process_errors {
96 0     0 1   my $self = shift;
97 0           my ($error, $ctx) = validate_params(\@_, [
98             { type => HASHREF },
99             { type => HASHREF }
100             ]);
101              
102 0           my $json;
103 0           my $alias = $self->alias;
104              
105 0           $self->log->debug("$alias: entering process_errors");
106              
107 0           $json = $self->_rpc_error($ctx->{'id'}, $error->{'code'}, $error->{'message'});
108              
109 0           $poe_kernel->post($alias, 'client_output', encode($json), $ctx);
110              
111             }
112              
113             # ----------------------------------------------------------------------
114             # Private Methods
115             # ----------------------------------------------------------------------
116              
117             sub _rpc_exception_handler {
118 0     0     my $self = shift;
119 0           my ($ex, $id) = validate_params(\@_, [1,1]);
120              
121 0           my $packet;
122 0           my $ref = ref($ex);
123              
124 0 0         if ($ref) {
125              
126 0 0         if ($ex->isa('XAS::Exception')) {
127              
128 0           my $type = $ex->type;
129 0           my $info = $ex->info;
130              
131 0 0         if ($type =~ /server\.rpc_method$/) {
    0          
    0          
    0          
132              
133 0           $packet = $self->_rpc_error($id, RPC_ERR_METHOD, $info);
134              
135             } elsif ($type =~ /server\.rpc_version$/) {
136              
137 0           $packet = $self->_rpc_error($id, RPC_ERR_REQ, $info);
138              
139             } elsif ($type =~ /server\.rpc_format$/) {
140              
141 0           $packet = $self->_rpc_error($id, RPC_ERR_PARSE, $info);
142              
143             } elsif ($type =~ /server\.rpc_notify$/) {
144              
145 0           $packet = $self->_rpc_error($id, RPC_ERR_INTERNAL, $info);
146              
147             } else {
148              
149 0           my $msg = $type . ' - ' . $info;
150 0           $packet = $self->_rpc_error($id, RPC_ERR_APP, $msg);
151              
152             }
153              
154 0           $self->log->error_msg('exception', $type, $info);
155              
156             } else {
157              
158 0           my $msg = sprintf("%s", $ex);
159              
160 0           $packet = $self->_rpc_error($id, RPC_ERR_SERVER, $msg);
161 0           $self->log->error_msg('unexpected', $msg);
162              
163             }
164              
165             } else {
166              
167 0           my $msg = sprintf("%s", $ex);
168              
169 0           $packet = $self->_rpc_error($id, RPC_ERR_APP, $msg);
170 0           $self->log->error_msg('unexpected', $msg);
171              
172             }
173              
174 0           return $packet;
175              
176             }
177              
178             sub _rpc_request {
179 0     0     my $self = shift;
180 0           my ($request, $ctx) = validate_params(\@_, [
181             { type => HASHREF },
182             { type => HASHREF },
183             ]);
184              
185 0           my $method;
186 0           my $alias = $self->alias;
187            
188             try {
189              
190 0 0   0     if ($request->{'jsonrpc'} ne RPC_JSON) {
191              
192 0           $self->throw_msg(
193             dotid($self->class) . '.server.rpc_version',
194             'json_rpc_version'
195             );
196              
197             }
198              
199 0 0         unless (defined($request->{'id'})) {
200              
201 0           $self->throw_msg(
202             dotid($self->class) . '.server.rpc_notify',
203             'json_rpc_notify'
204             );
205              
206             }
207              
208 0 0         if ($self->methods->has($request->{'method'})) {
209              
210 0           $ctx->{'id'} = $request->{'id'};
211 0           $self->log->debug("$alias: performing \"" . $request->{'method'} . '"');
212              
213 0           $poe_kernel->post($alias, $request->{'method'}, $request->{'params'}, $ctx);
214              
215             } else {
216              
217             $self->throw_msg(
218             dotid($self->class) . '.server.rpc_method',
219             'json_rpc_method',
220 0           $request->{'method'}
221             );
222              
223             }
224              
225             } catch {
226              
227 0     0     my $ex = $_;
228              
229 0           my $output = $self->_rpc_exception_handler($ex, $request->{'id'});
230 0           $poe_kernel->post($alias, 'client_output', encode($output), $ctx);
231              
232 0           };
233              
234             }
235              
236             sub _rpc_error {
237 0     0     my $self = shift;
238 0           my ($id, $code, $message) = validate_params(\@_, [1,1,1]);
239              
240             return {
241             jsonrpc => RPC_JSON,
242             id => $id,
243             error => {
244             code => $code,
245 0           message => $ERRORS->{$code},
246             data => $message
247             }
248             };
249              
250             }
251              
252             sub _rpc_result {
253 0     0     my $self = shift;
254 0           my ($id, $result) = validate_params(\@_, [1,1]);
255              
256             return {
257 0           jsonrpc => RPC_JSON,
258             id => $id,
259             result => $result
260             };
261              
262             }
263              
264             sub init {
265 0     0 1   my $class = shift;
266              
267 0           my $self = $class->SUPER::init(@_);
268              
269 0           $self->{'methods'} = Set::Light->new();
270              
271 0           return $self;
272              
273             }
274              
275             1;
276              
277             __END__
278              
279             =head1 NAME
280              
281             XAS::Lib::RPC::JSON::Server - A mixin for a simple JSON RPC server
282              
283             =head1 SYNOPSIS
284              
285             package Echo;
286              
287             use POE;
288             use XAS::Class
289             debug => 0,
290             version => '0.01',
291             base => 'XAS::Lib::RPC::JSON::Server'
292             ;
293              
294             sub session_initialize {
295             my $self = shift;
296              
297             my $alias = $self->alias;
298              
299             $self->log->debug("$alias: entering session_initialize()");
300              
301             # define our events.
302              
303             $poe_kernel->state('echo', $self, '_echo');
304              
305             # define the RPC methods, these are linked to the above events
306              
307             $self->methods->insert('echo');
308              
309             # walk the chain
310              
311             $self->SUPER::session_initialize();
312              
313             $self->log->debug("$alias: leaving session_initialize()");
314              
315             }
316              
317             sub _echo {
318             my ($self, $params, $ctx) = @_[OBJECT, ARGO, ARG1];
319              
320             my $alias = $self->alias;
321             my $line = $params->{'line'};
322              
323             $self->process_response($line, $ctx);
324              
325             }
326              
327             package main;
328              
329             my $echo = Echo->new();
330              
331             $echo->run();
332              
333             =head1 DESCRIPTION
334              
335             This modules implements a simple L<JSON RPC v2.0|http://www.jsonrpc.org/specification>
336             server. It doesn't support "Notification" calls.
337              
338             =head1 METHODS
339              
340             =head2 new
341              
342             This module inherits from L<XAS::Lib::Net::Server|XAS::Lib::Net::Server>
343             and accepts the same parameters.
344              
345             =head2 methods
346              
347             A handle to a L<Set::Light|https://metacpan.org/pod/Set::Light> object
348             that contains the methods that can be evoked.
349              
350             =head2 process_request($input, $ctx)
351              
352             This method accepts a JSON RPC packet and dispatches to the appropiate handler.
353             If a handler is not present, it signals an error and returns that to the client.
354              
355             =over 4
356              
357             =item B<$input>
358              
359             The JSON RPC packet.
360              
361             =item B<$ctx>
362              
363             Network context for the request.
364              
365             =back
366              
367             =head2 process_response($output, $ctx)
368              
369             This method will process output and convert it into a JSON RPC response.
370              
371             =over 4
372              
373             =item B<$input>
374              
375             The output from the called handler.
376              
377             =item B<$ctx>
378              
379             Network context for the response.
380              
381             =back
382              
383             =head2 process_error($error, $ctx)
384              
385             This method will process errors, it will be converted into a JSON RPC error
386             response.
387              
388             =over 4
389              
390             =item B<$errors>
391              
392             The errors that were generated.
393              
394             =item B<$ctx>
395              
396             Network context for the response.
397              
398             =back
399              
400             =head1 SEE ALSO
401              
402             =over 4
403              
404             =item L<XAS::Lib::RPC::JSON::Client|XAS::Lib::RPC::JSON::Client>
405              
406             =item L<XAS|XAS>
407              
408             =back
409              
410             =head1 AUTHOR
411              
412             Kevin L. Esteb, E<lt>kevin@kesteb.usE<gt>
413              
414             =head1 COPYRIGHT AND LICENSE
415              
416             Copyright (c) 2012-2015 Kevin L. Esteb
417              
418             This is free software; you can redistribute it and/or modify it under
419             the terms of the Artistic License 2.0. For details, see the full text
420             of the license at http://www.perlfoundation.org/artistic_license_2_0.
421              
422             =cut