File Coverage

blib/lib/NetSDS/App/JSRPC.pm
Criterion Covered Total %
statement 15 15 100.0
branch n/a
condition n/a
subroutine 5 5 100.0
pod n/a
total 20 20 100.0


line stmt bran cond sub pod time code
1             #===============================================================================
2             #
3             # FILE: JSRPC.pm
4             #
5             # DESCRIPTION: NetSDS admin
6             #
7             # NOTES: ---
8             # AUTHOR: Michael Bochkaryov (Rattler),
9             # COMPANY: Net.Style
10             # CREATED: 10.08.2009 20:57:57 EEST
11             #===============================================================================
12              
13             =head1 NAME
14              
15             NetSDS::App::JSRPC - JSON-RPC server framework
16              
17             =head1 SYNOPSIS
18              
19             #!/usr/bin/env perl
20             # JSON-RPC server
21            
22             use 5.8.0;
23             use warnings;
24             use strict;
25              
26             JServer->run();
27              
28             1;
29              
30             # Server application logic
31              
32             package JServer;
33              
34             use base 'NetSDS::App::JSRPC';
35              
36             # This method is available via JSON-RPC
37             sub sum {
38             my ($self, $param) = @_;
39             return $$param[0] + $$param[1];
40             }
41              
42             1;
43              
44             =head1 DESCRIPTION
45              
46             C module implements framework for common JSON-RPC based
47             server application. JSON-RPC is a HTTP based protocol providing remote
48             procudure call (RPC) functionality using JSON for requests and responses
49             incapsulation.
50              
51             This implementation is based on L module and expected to be
52             executed as FastCGI or CGI application.
53              
54             Diagram of class inheritance:
55              
56             [NetSDS::App::JSRPC] - JSON-RPC server
57             |
58             [NetSDS::App::FCGI] - CGI/FCGI application
59             |
60             [NetSDS::App] - common application
61             |
62             [NetSDS::Class::Abstract] - abstract class
63              
64             Both request and response are JSON-encoded strings represented in HTTP protocol
65             as data of 'application/json' MIME type.
66              
67              
68             =head1 APPLICATION DEVELOPMENT
69              
70             To develop new JSON-RPC server application you need to create application
71             class inherited from C:
72              
73             It's just empty application:
74              
75             #!/usr/bin/env perl
76            
77             JSApp->run(
78             conf_file => '/etc/NetSDS/jsonapp.conf'
79             );
80              
81             package JSApp;
82              
83             use base 'NetSDS::App::JSRPC';
84              
85             1;
86              
87             Alsoe you may want to add some specific code for application startup:
88              
89             sub start {
90             my ($self) = @_;
91              
92             connect_to_dbms();
93             query_for_external_startup_config();
94             do_other_initialization();
95              
96             }
97              
98             And of course you need to add methods providing necessary functions:
99              
100             sub send_sms {
101             my ($self, $params) = @_;
102              
103             return $self->{kannel}->send(
104             from => $params{'from'},
105             to => $params{'to'},
106             text => $params{'text'},
107             );
108             }
109              
110             sub kill_smsc {
111             my ($self, $params) = @_;
112              
113             # 1M of MT SM should be enough to kill SMSC!
114             # Otherwise we call it unbreakable :-)
115              
116             for (my $i=1; $<100000000; $i++) {
117             $self->{kannel}->send(
118             %mt_sm_parameters,
119             );
120             }
121              
122             if (smsc_still_alive()) {
123             return $self->error("Can't kill SMSC! Need more power!");
124             }
125             }
126              
127             =head1 ADVANCED FUNCTIONALITY
128              
129             C module provides two methods that may be used to implement
130             more complex logic than average RPC to one class.
131              
132             =over
133              
134             =item B - method availability checking
135              
136             By default it is just wrapper around C function.
137             However it may be rewritten to check for methods in other classes
138             or even construct necessary methods on the fly.
139              
140             =item B - method dispatching
141              
142             By default it just call local class method with the same name as in JSON-RPC call.
143             Of course it can be overwritten and process query in some other way.
144              
145             =back
146              
147             This code describes logic of call processing:
148              
149             # It's not real code
150              
151             if (can_method($json_method)) {
152             process_call($json_method, $json_params);
153             }
154              
155             For more details read documentation below.
156              
157             =cut
158              
159             package NetSDS::App::JSRPC;
160              
161 2     2   10066 use 5.8.0;
  2         9  
  2         90  
162 2     2   10 use strict;
  2         3  
  2         58  
163 2     2   11 use warnings;
  2         3  
  2         53  
164              
165 2     2   11 use JSON;
  2         3  
  2         17  
166 2     2   348 use base 'NetSDS::App::FCGI';
  2         5  
  2         337  
167              
168              
169             use version; our $VERSION = '1.301';
170              
171             #===============================================================================
172              
173             =head1 CLASS API
174              
175             =over
176              
177             =item B - class constructor
178              
179             It's internally used constructor that shouldn't be used from application directly.
180              
181             =cut
182              
183             #-----------------------------------------------------------------------
184             sub new {
185              
186             my ( $class, %params ) = @_;
187              
188             my $self = $class->SUPER::new(%params);
189              
190             return $self;
191              
192             }
193              
194             #***********************************************************************
195              
196             =item B - main JSON-RPC iteration
197              
198             This is internal method that implements JSON-RPC call processing.
199              
200             =cut
201              
202             #-----------------------------------------------------------------------
203              
204             sub process {
205              
206             my ($self) = @_;
207             # TODO - implement request validation
208             # Parse JSON-RPC2 request
209             my $http_request = $self->param('POSTDATA');
210              
211             # Set response MIME type
212             $self->mime('application/json');
213              
214             # Parse JSON-RPC call
215             if ( my ( $js_method, $js_params, $js_id ) = $self->_request_parse($http_request) ) {
216              
217             # Try to call method
218             if ( $self->can_method($js_method) ) {
219              
220             # Call method and hope it will give some response
221             my $result = $self->process_call( $js_method, $js_params );
222             if ( defined($result) ) {
223              
224             # Make positive response
225             $self->data(
226             $self->_make_result(
227             result => $result,
228             id => $js_id
229             )
230             );
231              
232             } else {
233              
234             # Can't get positive result
235             $self->data(
236             $self->_make_error(
237             code => -32000,
238             message => $self->errstr || "Error response from method $js_method",
239             id => undef,
240             )
241             );
242             }
243              
244             } else {
245              
246             # Can't find proper method
247             $self->data(
248             $self->_make_error(
249             code => -32601,
250             message => "Can't find JSON-RPC method",
251             id => undef,
252             )
253             );
254             }
255              
256             } else {
257              
258             # Send error object as a response
259             $self->data(
260             $self->_make_error(
261             code => -32700,
262             message => "Can't parse JSON-RPC call",
263             id => undef,
264             )
265             );
266             }
267              
268             } ## end sub process
269              
270              
271             #***********************************************************************
272              
273             =item B - check method availability
274              
275             This method allows to check if some method is available for execution.
276             By default it use C but may be rewritten to implement
277             more complex calls dispatcher.
278              
279             Paramters: method name (string)
280              
281             Return true if method execution allowed, false otherwise.
282              
283             Example:
284              
285             # Rewrite can_method() to search in other class
286             sub can_method {
287             my ($self, $method) = @_;
288             return Other::Class->can($method);
289             }
290              
291             =cut
292              
293             #-----------------------------------------------------------------------
294              
295             sub can_method {
296              
297             my ($self, $method) = @_;
298              
299             return $self->can($method);
300              
301             }
302              
303             #***********************************************************************
304              
305             =item B - execute method call
306              
307             Paramters: method name, parameters.
308              
309             Returns parameters from executed method as is.
310              
311             Example:
312              
313             # Rewrite process_call() to use other class
314             sub process_call {
315             my ( $self, $method, $params ) = @_;
316             return Other::Class->$method($params);
317             }
318              
319             =cut
320              
321             #-----------------------------------------------------------------------
322              
323             sub process_call {
324              
325             my ( $self, $method, $params ) = @_;
326              
327             return $self->$method($params);
328              
329             }
330              
331             #***********************************************************************
332              
333             =item B<_request_parse($post_data)> - parse HTTP POST
334              
335             Paramters: HTTP POST data as string
336              
337             Returns: request method, parameters, id
338              
339             =cut
340              
341             #-----------------------------------------------------------------------
342              
343             sub _request_parse {
344              
345             my ( $self, $post_data ) = @_;
346              
347             my $js_request = eval { decode_json($post_data) };
348             return $self->error("Can't parse JSON data") if $@;
349              
350             return ( $js_request->{'method'}, $js_request->{'params'}, $js_request->{'id'} );
351              
352             }
353              
354             #***********************************************************************
355              
356             =item B<_make_result(%params)> - prepare positive response
357              
358             This is internal method for encoding JSON-RPC response string.
359              
360             Paramters:
361              
362             =over
363              
364             =item B - the same as request Id (see specification)
365              
366             =item B - method result
367              
368             =back
369              
370             Returns JSON encoded response message.
371              
372             =cut
373              
374             #-----------------------------------------------------------------------
375              
376             sub _make_result {
377              
378             my ( $self, %params ) = @_;
379              
380             # Prepare positive response
381              
382             return encode_json(
383             {
384             jsonrpc => '2.0',
385             id => $params{'id'},
386             result => $params{'result'},
387             }
388             );
389              
390             }
391              
392             #***********************************************************************
393              
394             =item B<_make_error(%params)> - prepare error response
395              
396             Internal method implementing JSON-RPC error response.
397              
398             Paramters:
399              
400             =over
401              
402             =item B - the same as request Id (see specification)
403              
404             =item B - error code (default is -32603, internal error)
405              
406             =item B - error message
407              
408             =back
409              
410             Returns JSON encoded error message
411              
412             =cut
413              
414             #-----------------------------------------------------------------------
415              
416             sub _make_error {
417              
418             my ( $self, %params ) = @_;
419              
420             # Prepare error code and message
421             # http://groups.google.com/group/json-rpc/web/json-rpc-1-2-proposal
422              
423             my $err_code = $params{code} || -32603; # Internal JSON-RPC error.
424             my $err_msg = $params{message} || "Internal error.";
425              
426             # Return JSON encoded error object
427             return encode_json(
428             {
429             jsonrpc => '2.0',
430             id => $params{'id'},
431             error => {
432             code => $err_code,
433             message => $err_msg,
434             },
435             }
436             );
437              
438             } ## end sub _make_error
439              
440             1;
441              
442             __END__