File Coverage

blib/lib/JSON/RPC2/TwoWay/Connection.pm
Criterion Covered Total %
statement 83 108 76.8
branch 40 66 60.6
condition 18 35 51.4
subroutine 16 20 80.0
pod 7 9 77.7
total 164 238 68.9


line stmt bran cond sub pod time code
1             package JSON::RPC2::TwoWay::Connection;
2              
3 2     2   25 use 5.10.0;
  2         6  
4 2     2   10 use strict;
  2         3  
  2         45  
5 2     2   9 use warnings;
  2         3  
  2         86  
6              
7             our $VERSION = '0.06'; # VERSION
8              
9             # standard perl
10 2     2   10 use Carp;
  2         5  
  2         100  
11 2     2   11 use Data::Dumper;
  2         2  
  2         102  
12 2     2   10 use Digest::MD5 qw(md5_base64);
  2         4  
  2         82  
13 2     2   9 use Scalar::Util qw(refaddr weaken);
  2         11  
  2         110  
14              
15             # cpan
16 2     2   10 use JSON::MaybeXS qw();
  2         4  
  2         43  
17              
18 2     2   16 use constant ERR_REQ => -32600;
  2         5  
  2         2238  
19              
20             sub new {
21 2     2 1 9 my ($class, %opt) = @_;
22 2 50 33     16 croak 'no rpc?' unless $opt{rpc} and $opt{rpc}->isa('JSON::RPC2::TwoWay');
23             #croak 'no stream?' unless $opt->{stream} and $opt->{stream}->can('write');
24 2 100 66     83 croak 'no write?' unless $opt{write} and ref $opt{write} eq 'CODE';
25             my $self = {
26             calls => {},
27             debug => $opt{debug} // 0,
28             next_id => 1,
29             owner => $opt{owner},
30             request => undef,
31             rpc => $opt{rpc},
32             json => $opt{rpc}->{json},
33             state => undef,
34             write => $opt{write},
35 1   50     10 };
36 1         4 weaken $self->{owner};
37 1         4 return bless $self, $class;
38             }
39              
40             sub call {
41 5     5 1 2735 my ($self, $name, $args, $cb) = @_;
42 5 50       13 croak 'no self?' unless $self;
43 5 100 66     102 croak 'args should be a array or hash reference'
44             unless ref $args eq 'ARRAY' or ref $args eq 'HASH';
45 4 100       65 croak 'no callback?' unless $cb;
46 3 100       65 croak 'callback should be a code reference' if ref $cb ne 'CODE';
47 2         22 my $id = md5_base64($self->{next_id}++ . $name . $self->{json}->encode($args) . refaddr($cb));
48 2 50       6 croak 'duplicate call id' if $self->{calls}->{$id};
49             my $request = $self->{json}->encode({
50 2         13 jsonrpc => '2.0',
51             method => $name,
52             params => $args,
53             id => $id,
54             });
55 2         7 $self->{calls}->{$id} = [ $cb, 0 ]; # not raw
56             #say STDERR "call: $request" if $self->{debug};
57 2         6 $self->write($request);
58 2         9 return;
59             }
60              
61             sub callraw {
62 0     0 1 0 my ($self, $request, $cb) = @_;
63 0 0       0 croak 'no self?' unless $self;
64 0 0       0 croak 'request should be a array or hash reference'
65             unless ref $request eq 'HASH';
66 0 0       0 croak 'callback should be a code reference' if ref $cb ne 'CODE';
67 0         0 my $id = md5_base64($self->{next_id}++ . $self->{json}->encode($request) . refaddr($cb));
68 0 0       0 croak 'duplicate call id' if $self->{calls}->{$id};
69 0         0 $request->{jsonrpc} = '2.0';
70 0         0 $request->{id} = $id;
71 0         0 $request = $self->{json}->encode($request);
72 0         0 $self->{calls}->{$id} = [ $cb, 1 ]; # raw
73             #say STDERR "callraw: $request" if $self->{debug};
74 0         0 $self->write($request);
75 0         0 return;
76             }
77              
78             sub notify {
79 0     0 1 0 my ($self, $name, $args, $cb) = @_;
80 0 0       0 croak 'no self?' unless $self;
81 0 0 0     0 croak 'args should be a array of hash reference'
82             unless ref $args eq 'ARRAY' or ref $args eq 'HASH';
83             my $request = $self->{json}->encode({
84 0         0 jsonrpc => '2.0',
85             method => $name,
86             params => $args,
87             });
88             #say STDERR "notify: $request" if $self->{debug};
89 0         0 $self->write($request);
90 0         0 return;
91             }
92              
93             sub handle {
94 17     17 1 8696 my ($self, $json) = @_;
95 17         40 my @err = $self->_handle(\$json);
96 17 100       51 $self->{rpc}->_error($self, undef, ERR_REQ, 'Invalid Request: ' . $err[0]) if $err[0];
97 17         40 return @err;
98             }
99              
100             sub _handle {
101 17     17   22 my ($self, $jsonr) = @_;
102 17 50       38 say STDERR ' handle: ', $$jsonr if $self->{debug};
103 17         20 local $@;
104 17         29 my $r = eval { $self->{json}->decode($$jsonr) };
  17         97  
105 17 100       39 return "json decode failed: $@" if $@;
106 16 100       35 return 'not a json object' if ref $r ne 'HASH';
107 15 100 66     59 return 'expected jsonrpc version 2.0' unless defined $r->{jsonrpc} and $r->{jsonrpc} eq '2.0';
108             # id can be null in the error case
109 14 100 100     45 return 'id is not a string or number' if exists $r->{id} and ref $r->{id};
110 13 100 100     35 if (defined $r->{method}) {
    100 33        
111 9         24 return $self->{rpc}->_handle_request($self, $r);
112             } elsif (exists $r->{id} and (exists $r->{result} or defined $r->{error})) {
113 3         9 return $self->_handle_response($r);
114             } else {
115 1         4 return 'invalid jsonnrpc object';
116             }
117             }
118              
119             sub _handle_response {
120 3     3   5 my ($self, $r) = @_;
121             #say STDERR '_handle_response: ', Dumper($r) if $self->{debug};
122 3         4 my $id = $r->{id};
123 3         6 my ($cb, $raw);
124 3 50       8 $cb = delete $self->{calls}->{$id} if $id;
125 3 100       9 return unless $cb;
126 2         4 ($cb, $raw) = @$cb;
127 2 100       7 if (defined $r->{error}) {
128 1         1 my $e = $r->{error};
129 1 50       4 return 'error is not an object' unless ref $e eq 'HASH';
130 1 50 33     13 return 'error code is not a integer' unless defined $e->{code} and $e->{code} =~ /^-?\d+$/;
131 1 50       3 return 'error message is not a string' if ref $e->{message};
132 1 50 33     8 return 'extra members in error object' if (keys %$e == 3 and !exists $e->{data}) or (keys %$e > 2);
      33        
133 1 50       3 if ($raw) {
134 0         0 $cb->($r);
135             } else {
136 1         4 $cb->($e);
137             }
138             } else {
139 1 50       4 if ($raw) {
140 0         0 $cb->(0, $r);
141             } else {
142 1         27 $cb->(0, $r->{result});
143             }
144             }
145 2         30 return;
146             }
147              
148             sub write {
149 16     16 0 25 my $self = shift;
150 16 50       37 say STDERR ' writing: ', @_ if $self->{debug};
151 16         36 $self->{write}->(@_);
152             }
153              
154             sub owner {
155 0     0 1 0 my $self = shift;
156 0 0       0 weaken ($self->{owner} = shift) if (@_);
157 0         0 return $self->{owner};
158             }
159              
160             sub state {
161 4     4 0 427 my $self = shift;
162 4 100       8 $self->{state} = shift if (@_);
163 4         17 return $self->{state};
164             }
165              
166              
167             sub close {
168 0     0 1   my $self = shift;
169 0           %$self = (); # nuke'm all
170             }
171              
172             #sub DESTROY {
173             # my $self = shift;
174             # say STDERR 'destroying ', $self;
175             #}
176              
177             1;
178              
179             =encoding utf8
180              
181             =head1 NAME
182              
183             JSON::RPC2::TwoWay::Connection - Transport-independent bidirectional JSON-RPC 2.0 connection
184              
185             =head1 SYNOPSIS
186              
187             $rpc = JSON::RPC2::TwoWay->new();
188             $rpc->register('ping', \&handle_ping);
189              
190             $con = $rpc->newconnection(
191             owner => $owner,
192             write => sub { $stream->write(@_) }
193             );
194             @err = $con->handle($stream->read);
195             die $err[-1] if @err;
196              
197             =head1 DESCRIPTION
198              
199             L is a connection containter for
200             L.
201              
202             =head1 METHODS
203              
204             =head2 new
205              
206             $con = JSON::RPC2::TwoWay::Connection->new(option => ...);
207              
208             Class method that returns a new JSON::RPC2::TwoWay::Connection object.
209             Use newconnection() on a L object instead.
210              
211             Valid arguments are:
212              
213             =over 4
214              
215             =item - debug: print debugging to STDERR
216              
217             (default false)
218              
219             =item - owner: 'owner' object of this connection.
220              
221             When provided this object will be asked for the 'state' of the connection.
222             Otherwise state will always be 0.
223              
224             =item - rpc: the L object to handle incoming method calls
225              
226             (required)
227              
228             =item - write: a coderef called for writing
229              
230             This coderef will be called for all output: both requests and responses.
231             (required)
232              
233             =back
234              
235             =head2 call
236              
237             $con->call('method', { arg => 'foo' }, $cb);
238              
239             Calls the remote method indicated in the first argument.
240              
241             The second argument should either be a arrayref or hashref, depending on
242             wether the remote method requires positional of by-name arguments. Pass a
243             empty reference when there are no arguments.
244              
245             The third argument is a callback: this callback will
246             be called with the results of the called method.
247              
248             Call throws an error in case of missing arguments, otherwise it returns
249             immediately with no return value.
250              
251             =head2 callraw
252              
253             $con->callraw({ method => 'method', params => {..} }, $cb);
254              
255             Enhances the first argument (which should be a hashref) to a full JSON-RPC
256             2.0 request object and sends the request. This allows for manipulating and
257             extending the actual request.
258              
259             The third argument is a callback: this callback will
260             be called with the results of the called method.
261              
262             Callraw throws an error in case of missing arguments, otherwise it returns
263             immediately with no return value.
264              
265             =head3 the result callback
266              
267             The result callback is called with 1 or 2 arguments. The first argument is
268             a protocol-error-flag, it contains a error message when there was some kind
269             of protocol error like calling a normal method as a notification.
270              
271             If there are 2 arguments the first one is always false, the second one will
272             contain the results from the remote method, see "REGISTERED CALLBACK CALLING
273             CONVENTION" in "L. The full response will be passed for
274             access to any extra fields.
275              
276             =head2 notify
277              
278             $con->notify('notify_me', { baz => 'foo' })
279              
280             Calls the remote method as a notification, i.e. no response will be
281             expected. Notify throws an error in case of missing arguments, otherwise it
282             returns immediately with no return value.
283              
284             =head2 handle
285              
286             $con->handle($jsonblob)
287              
288             Handle the incoming request or response. Requests (if valid) are passed on
289             to the registered callback for that method. Repsonses (if valid) are passed
290             on to the callback provided in the call.
291              
292             Handle returns 0, 1 or 2 values. If no value is returned there were no
293             errors during processing. If 1 value is returned there was a 'fatal' error,
294             and the value is the error message. If 2 values are returned there was a
295             'normal' error, the first value is false, the second value is the error
296             message.
297              
298             In case of an error, handle will call the provided write callback with a
299             appropriate error response to be sent to the other side. The application
300             using the JSON::RPC2::TwoWay::Connection is advised to close the underlying
301             connection in case of fatal errors.
302              
303             =head2 close
304              
305             $con->close()
306              
307             Closes the connection. Recommended to be used to avoid memory leaks due to
308             circular references.
309              
310             =head2 owner
311              
312             Getter-setter to allow the application to connect the
313             JSON::RPC2::TwoWay::Connection to some internal connection concept.
314              
315             -head2 state
316              
317             Getter-setter for the connection state. Evaluated by JSON::RPC2::TwoWay
318             when a method was registered with a state option.
319              
320             =head1 SEE ALSO
321              
322             =over
323              
324             =item *
325              
326             L
327              
328             =item *
329              
330             L: JSON-RPC 2.0 Specification
331              
332             =back
333              
334             =head1 ACKNOWLEDGEMENT
335              
336             This software has been developed with support from L.
337             In German: Diese Software wurde mit Unterstützung von L entwickelt.
338              
339             =head1 AUTHORS
340              
341             =over 4
342              
343             =item *
344              
345             Wieger Opmeer
346              
347             =back
348              
349             =head1 COPYRIGHT AND LICENSE
350              
351             This software is copyright (c) 2016-2019 by Wieger Opmeer.
352              
353             This is free software; you can redistribute it and/or modify it under
354             the same terms as the Perl 5 programming language system itself.
355              
356             =cut
357