File Coverage

lib/MojoX/Dispatcher/Qooxdoo/Jsonrpc.pm
Criterion Covered Total %
statement 92 112 82.1
branch 29 38 76.3
condition 7 15 46.6
subroutine 9 10 90.0
pod 0 1 0.0
total 137 176 77.8


line stmt bran cond sub pod time code
1             package MojoX::Dispatcher::Qooxdoo::Jsonrpc;
2              
3 1     1   651 use strict;
  1         2  
  1         35  
4 1     1   5 use warnings;
  1         1  
  1         30  
5              
6 1     1   16 use Mojo::JSON qw(encode_json decode_json);
  1         1  
  1         55  
7 1     1   4 use Mojo::Base 'Mojolicious::Controller';
  1         1  
  1         8  
8 1     1   98062 use Encode;
  1         2  
  1         102  
9 1     1   8 use Data::Dumper;
  1         2  
  1         112  
10              
11             our $toUTF8 = find_encoding('utf8');
12              
13             BEGIN {
14 1 50   1   680 warn "MojoX::Dispatcher::Qooxdoo::Jsonrpc is DEPRECATED. Please switch to using Mojolicious::Plugin::Qooxdoo.\n" unless $ENV{DISABLE_DEPRECATION_WARNING_MPQ};
15             }
16              
17              
18             our $VERSION = '0.96';
19              
20             sub dispatch {
21 9     9 0 169030 my $self = shift;
22            
23             # We have to differentiate between POST and GET requests, because
24             # the data is not sent in the same place..
25 9         168 my $log = $self->app->log;
26              
27             # send warnings to log file preserving the origin
28             local $SIG{__WARN__} = sub {
29 0     0   0 my $message = shift;
30 0         0 $message =~ s/\n$//;
31 0         0 @_ = ($log, $message);
32 0         0 goto &Mojo::Log::warn;
33 9         240 };
34 9         17 my $id;
35             my $data;
36 0         0 my $cross_domain;
37 9         32 for ( $self->req->method ){
38 9 100       543 /^POST$/ && do {
39             # Data comes as JSON object, so fetch a reference to it
40 8         14 $data = eval { decode_json($self->req->body) };
  8         25  
41 8 100       4838 if ($@) {
42 1         8 my $error = "Invalid json string: " . $@;
43 1         47 $log->error($error);
44 1         27 $self->render(text => $error, status=>500);
45 1         898 return;
46             };
47 7         18 $id = $data->{id};
48 7         12 $cross_domain = 0;
49 7         18 next;
50             };
51 1 50       6 /^GET$/ && do {
52 1         10 my $v = $self->param('_ScriptTransport_data');
53 1         553 $data = eval { decode_json($self->param('_ScriptTransport_data')) };
  1         4  
54 1 50       291 if ($@){
55 0         0 my $error = "Invalid json string: " . $@ . " " .Dumper $self->param;
56 0         0 $log->error($error);
57 0         0 $self->render(text => $error, status=>500);
58 0         0 return;
59             };
60              
61 1         6 $id = $self->param('_ScriptTransport_id') ;
62 1         108 $cross_domain = 1;
63 1         3 next;
64             };
65 0         0 my $error = "request must be POST or GET. Can't handle '".$self->req->method."'";
66 0         0 $log->error($error);
67 0         0 $self->render(text => $error, status=>500);
68 0         0 return;
69             }
70 8 100       30 if (not defined $id){
71 1         2 my $error = "Missing 'id' property in JsonRPC request.";
72 1         5 $log->error($error);
73 1         20 $self->render(text => $error, status=>500);
74 1         786 return;
75             }
76              
77              
78             # Check if desired service is available
79 7 100       26 my $service = $data->{service} or do {
80 1         2 my $error = "Missing service property in JsonRPC request.";
81 1         6 $log->error($error);
82 1         20 $self->render(text => $error, status=>500);
83 1         800 return;
84             };
85              
86             # Check if method is not private (marked with a leading underscore)
87 6 100       26 my $method = $data->{method} or do {
88 1         2 my $error = "Missing method property in JsonRPC request.";
89 1         4 $log->error($error);
90 1         26 $self->render(text => $error, status=>500);
91 1         774 return;
92             };
93            
94 5   100     33 my $params = $data->{params} || []; # is a reference, so "unpack" it
95            
96             # invocation of method in class according to request
97 5         12 my $reply = eval{
98             # make sure there are not foreign signal handlers
99             # messing with our problems
100 5         26 local $SIG{__DIE__};
101             # Getting available services from stash
102 5         23 my $svc = $self->stash('services')->{$service};
103              
104 5 100       84 die {
105             origin => 1,
106             message => "service $service not available",
107             code=> 2
108             } if not ref $svc;
109              
110 4 50       28 die {
111             origin => 1,
112             message => "your rpc service object (".ref($svc).") must provide an allow_rpc_access method",
113             code=> 2
114             } unless $svc->can('allow_rpc_access');
115              
116            
117 4 50       19 if ($svc->can('controller')){
118             # initialize session if it does not exists yet
119 0         0 $svc->controller($self);
120             }
121              
122 4 50       16 if ($svc->can('mojo_session')){
123             # initialize session if it does not exists yet
124 0         0 $log->warn('mojo_session is deprecated. Use controller->session instead');
125 0   0     0 my $session = $self->stash->{'mojo.session'} ||= {};
126 0         0 $svc->mojo_session($session);
127             }
128              
129 4 50       17 if ($svc->can('mojo_stash')){
130 0         0 $log->warn('mojo_stash is deprecated. Use controller->stash instead');
131             # initialize session if it does not exists yet
132 0         0 $svc->mojo_stash($self->stash);
133             }
134              
135             die {
136 4 100       14 origin => 1,
137             message => "rpc access to method $method denied",
138             code=> 6
139             } unless $svc->allow_rpc_access($method);
140              
141 3 50       11 die {
142             origin => 1,
143             message => "method $method does not exist.",
144             code=> 4
145             } if not $svc->can($method);
146              
147 3         15 $log->debug("call $method(".encode_json($params).")");
148             # reply
149 1     1   6 no strict 'refs';
  1         2  
  1         441  
150 3         215 $svc->$method(@$params);
151             };
152            
153 5 100       75 if ($@){
154 3         24 my $error;
155 3         10 for (ref $@){
156 3 100 66     23 /HASH/ && $@->{message} && do {
157 2   50     11 $error = {
158             origin => $@->{origin} || 2,
159             message => $@->{message},
160             code=>$@->{code}
161             };
162 2         4 last;
163             };
164 1 50 33     23 /.+/ && $@->can('message') && $@->can('code') && do {
      33        
165 1         18 $error = {
166             origin => 2,
167             message => $@->message(),
168             code=>$@->code()
169             };
170 1         21 last;
171             };
172 0         0 $error = {
173             origin => 2,
174             message => "error while processing ${service}::$method: $@",
175             code=> 9999
176             };
177             }
178 3         15 $reply = encode_json({ id => $id, error => $error });
179 3         312 $log->error("JsonRPC Error $error->{code}: $error->{message}");
180             }
181             else {
182 2         11 $reply = encode_json({ id => $id, result => $reply });
183 2         95 $log->debug("return ".$reply);
184             }
185              
186 5 100       87 if ($cross_domain){
187             # for GET requests, qooxdoo expects us to send a javascript method
188             # and to wrap our json a litte bit more
189 1         5 $self->res->headers->content_type('application/javascript; charset=utf-8');
190 1         154 $reply = "qx.io.remote.transport.Script._requestFinished( $id, " . $reply . ");";
191             } else {
192 4         17 $self->res->headers->content_type('application/json; charset=utf-8');
193             }
194             # the render takes care of encoding the output, so make sure we re-decode
195             # the json stuf
196 5         392 $self->render(text => $toUTF8->decode($reply));
197             }
198              
199             1;
200              
201              
202              
203             =head1 NAME
204              
205             MojoX::Dispatcher::Qooxdoo::Jsonrpc - Dispatcher for Qooxdoo Json Rpc Calls
206              
207             =head1 SYNOPSIS
208              
209             THIS MODULE IS DEPRECATED. USE L INSTEAD.
210              
211             # lib/your-application.pm
212              
213             use base 'Mojolicious';
214            
215             use RpcService;
216              
217             sub startup {
218             my $self = shift;
219            
220             # instantiate all services
221             my $services= {
222             Test => RpcService->new(),
223            
224             };
225            
226            
227             # add a route to the Qooxdoo dispatcher and route to it
228             my $r = $self->routes;
229             $r->route('/qooxdoo') -> to(
230             'Jsonrpc#dispatch',
231             services => $services,
232             debug => 0,
233             namespace => 'MojoX::Dispatcher::Qooxdoo'
234             );
235            
236             }
237              
238            
239              
240             =head1 DESCRIPTION
241              
242             L dispatches incoming
243             rpc requests from a qooxdoo application to your services and renders
244             a (hopefully) valid json reply.
245              
246              
247             =head1 EXAMPLE
248              
249             This example exposes a service named "Test" in a folder "RpcService".
250             The Mojo application is named "QooxdooServer". The scripts are in
251             the 'example' directory.
252             First create this application using
253             "mojolicious generate app QooxdooServer".
254              
255             Then, lets write the service:
256              
257             Change to the root directory "qooxdoo_server" of your fresh
258             Mojo-Application and make a dir named 'qooxdoo-services'
259             for the services you want to expose.
260              
261             Our "Test"-service could look like:
262              
263             package RpcService;
264              
265             use Mojo::Base -base;
266              
267             # if you want to access mojo specific information
268             # provide a controller property, it will be set to the
269             # current controller as the request is dispached.
270             # see L for documentation.
271             has 'controller';
272            
273             # MANDADROY access check method. The method is called right before the actual
274             # method call, after assigning mojo_session and mojo_stash properties are set.
275             # These can be used for providing dynamic access control
276              
277             our %access = (
278             add => 1,
279             );
280              
281             sub allow_rpc_access {
282             my $self = shift;
283             my $method = shift;
284             # check if we can access
285             return $access{$method};
286             }
287              
288             sub add{
289             my $self = shift;
290             my @params = @_;
291            
292             # Debug message on Mojo-server console (or log)
293             print "Debug: $params[0] + $params[1]\n";
294            
295             # uncomment if you want to die without further handling
296             # die;
297            
298             # uncomment if you want to die with a message in a hash
299             # die {code => 20, message => "Test died on purpose :-)"};
300            
301            
302             # uncomment if you want to die with your homemade error object
303             # die MyException->new(code=>123,message=>'stupid error message');
304            
305             my $result = $params[0] + $params[1]
306             return $result;
307             }
308              
309             package MyException;
310             use Mojo::Base -base;
311             has 'code';
312             has 'message';
313             1;
314              
315             The Dispatcher executes all calls to your service module within an eval
316             wrapper and will send any execptions you generate within back to the
317             qooxdoo application as well as into the Mojolicious logfile.
318              
319             Now, lets write our application. Normally one would use the services of
320             L for this. If you want to use the
321             dipatcher directly, this is how it is done.
322              
323             package QooxdooServer;
324              
325             use strict;
326             use warnings;
327            
328             use RpcService::Test;
329              
330             use Mojo::Base 'Mojolicious';
331              
332             # This method will run once at server start
333             sub startup {
334             my $self = shift;
335            
336             my $services= {
337             Test => RpcService::Test->new(),
338             # more services here
339             };
340            
341             # tell Mojo about your services:
342             my $r = $self->routes;
343            
344             # this sends all requests for "/qooxdoo" in your Mojo server
345             # to our little dispatcher.
346             # change this at your own taste.
347             $r->route('/qooxdoo')->to('
348             jsonrpc#dispatch',
349             services => $services,
350             namespace => 'MojoX::Dispatcher::Qooxdoo'
351             );
352            
353             }
354              
355             1;
356              
357             Now start your Mojo Server by issuing C