File Coverage

blib/lib/JSON/RPC/Dispatch.pm
Criterion Covered Total %
statement 116 122 95.0
branch 36 42 85.7
condition 6 8 75.0
subroutine 18 18 100.0
pod 0 5 0.0
total 176 195 90.2


line stmt bran cond sub pod time code
1             package JSON::RPC::Dispatch;
2 2     2   43585 use strict;
  2         3  
  2         62  
3 2     2   282 use JSON::RPC::Constants qw(:all);
  2         2  
  2         233  
4 2     2   612 use JSON::RPC::Parser;
  2         4  
  2         53  
5 2     2   10 use JSON::RPC::Procedure;
  2         2  
  2         31  
6 2     2   881 use Router::Simple;
  2         5025  
  2         48  
7 2     2   11 use Scalar::Util;
  2         2  
  2         63  
8 2     2   925 use Try::Tiny;
  2         2213  
  2         120  
9              
10             use Class::Accessor::Lite
11 2         11 rw => [ qw(
12             coder
13             handlers
14             parser
15             prefix
16             router
17             ) ]
18 2     2   11 ;
  2         2  
19              
20             sub new {
21 2     2 0 4128 my ($class, @args) = @_;
22 2         8 my $self = bless {
23             handlers => {},
24             @args,
25             }, $class;
26 2 100       10 if (! $self->{coder}) {
27 1         7 require JSON;
28 1         14 $self->{coder} = JSON->new->utf8;
29             }
30 2 100       6 if (! $self->{parser}) {
31 1         4 $self->{parser} = JSON::RPC::Parser->new( coder => $self->coder )
32             }
33 2 100       18 if (! $self->{router}) {
34 1         6 $self->{router} = Router::Simple->new;
35             }
36 2         8 return $self;
37             }
38              
39             sub guess_handler_class {
40 10     10 0 10 my ($self, $klass) = @_;
41              
42 10   50     20 my $prefix = $self->prefix || '';
43 10         53 return "$prefix\::$klass";
44             }
45              
46             sub construct_handler {
47 10     10 0 9 my ($self, $klass) = @_;
48              
49 10         24 my $handler = $self->handlers->{ $klass };
50 10 100       45 if (! $handler) {
51 1         55 eval "require $klass";
52 1 50       6 die if $@;
53 1         5 $handler = $klass->new();
54 1         8 $self->handlers->{$klass} = $handler;
55             }
56 10         15 return $handler;
57             }
58              
59             sub get_handler {
60 12     12 0 12 my ($self, $klass) = @_;
61              
62 12 100       33 if ( Scalar::Util::blessed( $klass )){
63 2         2 if (JSONRPC_DEBUG > 1) {
64             warn "Handler is already object : $klass";
65             }
66 2         2 return $klass;
67             }
68              
69 10 50       24 if ($klass !~ s/^\+//) {
70 10         19 $klass = $self->guess_handler_class( $klass );
71             }
72              
73 10         18 my $handler = $self->construct_handler( $klass );
74 10         10 if (JSONRPC_DEBUG > 1) {
75             warn "$klass -> $handler";
76             }
77 10         12 return $handler;
78             }
79              
80             sub handle_psgi {
81 17     17 0 24 my ($self, $req, @args) = @_;
82              
83 17 50       48 if ( ! Scalar::Util::blessed($req) ) {
84             # assume it's a PSGI hash
85 17         82 require Plack::Request;
86 17         68 $req = Plack::Request->new($req);
87             }
88              
89 17         101 my $is_batch = 0;
90 17         19 my @response;
91             my $procedures;
92             try {
93 17     17   318 $procedures = $self->parser->construct_from_req( $req );
94 14 100       36 if (ref $procedures eq 'ARRAY') {
95 4         4 $is_batch = 1;
96             } else {
97 10         16 $procedures = [$procedures];
98             }
99 14 100 66     81 if (@$procedures <= 0 || not defined $procedures->[0]) {
100 1         1 $is_batch = 0;
101 1         5 push @response, {
102             error => {
103             code => RPC_INVALID_REQUEST,
104             message => "Could not find any procedures"
105             }
106             };
107             }
108             } catch {
109 3     3   126 my $e = $_;
110 3         3 if (JSONRPC_DEBUG) {
111             warn "error while creating jsonrpc request: $e";
112             }
113 3 100       14 if ($e =~ /Invalid parameter/) {
    100          
114 1         5 push @response, {
115             error => {
116             code => RPC_INVALID_PARAMS,
117             message => "Invalid parameters",
118             }
119             };
120             } elsif ( $e =~ /parse error/ ) {
121 1         5 push @response, {
122             error => {
123             code => RPC_PARSE_ERROR,
124             message => "Failed to parse json",
125             }
126             };
127             } else {
128 1         4 push @response, {
129             error => {
130             code => RPC_INVALID_REQUEST,
131             message => $e
132             }
133             }
134             }
135 17         81 };
136              
137 17         223 my $router = $self->router;
138 17         68 foreach my $procedure (@$procedures) {
139 15 50       31 if ( ! $procedure->{method} ) {
140 0         0 my $message = "Procedure name not given";
141 0         0 if (JSONRPC_DEBUG) {
142             warn $message;
143             }
144 0         0 push @response, {
145             error => {
146             code => RPC_METHOD_NOT_FOUND,
147             message => $message,
148             }
149             };
150 0         0 next;
151             }
152              
153 15   100     29 my $is_notification = defined $procedure->jsonrpc && $procedure->jsonrpc eq '2.0' && !$procedure->has_id;
154 15         159 my $matched = $router->match( $procedure->{method} );
155 15 100       664 if (! $matched) {
156 3         6 my $message = "Procedure '$procedure->{method}' not found";
157 3         3 if (JSONRPC_DEBUG) {
158             warn $message;
159             }
160 3 100       7 if (!$is_notification) { # must not respond to a valid JSON-RPC notification
161 2         8 push @response, {
162             error => {
163             code => RPC_METHOD_NOT_FOUND,
164             message => $message,
165             }
166             };
167             }
168 3         7 next;
169             }
170              
171 12         17 my $action = $matched->{action};
172             try {
173 12     12   241 my ($ip, $ua);
174 12         8 if (JSONRPC_DEBUG > 1) {
175             warn "Procedure '$procedure->{method}' maps to action $action";
176             $ip = $req->address || 'N/A';
177             $ua = $req->user_agent || 'N/A';
178             }
179 12         26 my $params = $procedure->params;
180 12         54 my $handler = $self->get_handler( $matched->{handler} );
181              
182 12         32 my $code = $handler->can( $action );
183 12 50       20 if (! $code) {
184 0         0 if ( JSONRPC_DEBUG ) {
185             warn "[INFO] handler $handler does not implement method $action!.";
186             }
187 0         0 die "Internal Error";
188             }
189 12         22 my $result = $code->( $handler, $procedure->params, $procedure, @args );
190 5         58 if (JSONRPC_DEBUG) {
191             warn "[INFO] action=$action "
192             . "params=["
193             . (ref $params ? $self->{coder}->encode($params) : $params)
194             . "] ret="
195             . (ref $result ? $self->{coder}->encode($result) : $result)
196             . " IP=$ip UA=$ua";
197             }
198              
199             # respond unless we are sure a procedure is a notification
200 5 50       12 if (!$is_notification) {
201 5         12 push @response, {
202             jsonrpc => '2.0',
203             result => $result,
204             id => $procedure->id,
205             };
206             }
207             } catch {
208 7     7   155 my $e = $_;
209 7         5 if (JSONRPC_DEBUG) {
210             warn "Error while executing $action: $e";
211             }
212             # can't respond to notifications even in case of errors
213 7 100       42 if (!$is_notification) {
214 4         8 my $error = {code => RPC_INTERNAL_ERROR} ;
215 4 100       8 if (ref $e eq "HASH") {
216 1         3 $error->{message} = $e->{message},
217             $error->{data} = $e->{data},
218             } else {
219 3         6 $error->{message} = $e,
220             }
221 4         8 push @response, {
222             jsonrpc => '2.0',
223             id => $procedure->id,
224             error => $error,
225             };
226             }
227 12         86 };
228             }
229              
230 17         156 my $res;
231 17 100       27 if (scalar @response) {
232 15         39 $res = $req->new_response(200);
233 15         2319 $res->content_type( 'application/json; charset=utf8' );
234 15 100       315 $res->body(
235             $self->coder->encode( ($is_batch) ? \@response : $response[0] )
236             );
237 15         221 return $res->finalize;
238             } else { # no content
239 2         6 $res = $req->new_response(204);
240             }
241              
242 2         38 return $res->finalize;
243             }
244              
245 2     2   2282 no Try::Tiny;
  2         3  
  2         49  
246              
247             1;
248              
249             __END__