File Coverage

blib/lib/JSONRPC/Transport/TCP.pm
Criterion Covered Total %
statement 24 87 27.5
branch 1 38 2.6
condition 0 21 0.0
subroutine 8 13 61.5
pod 4 4 100.0
total 37 163 22.7


line stmt bran cond sub pod time code
1             package JSONRPC::Transport::TCP;
2 3     3   27334 use strict;
  3         7  
  3         113  
3 3     3   19 use warnings;
  3         5  
  3         118  
4 3     3   17 use base qw/Class::Accessor::Fast/;
  3         7  
  3         1323  
5              
6             __PACKAGE__->mk_accessors(qw/result error/);
7              
8 3     3   7637 use IO::Select;
  3         8847  
  3         188  
9 3     3   16292 use IO::Socket::INET;
  3         56375  
  3         32  
10 3     3   2235 use IO::Socket::UNIX;
  3         6  
  3         25  
11 3     3   3682 use Carp;
  3         7  
  3         380  
12              
13             our $VERSION = '0.04';
14             our $XS_AVAILABLE = 1;
15              
16             BEGIN {
17 3     3   7 eval { require JSON::XS };
  3         3776  
18 3 50       27573 if ($@) {
19 0           $XS_AVAILABLE = 0;
20 0           require JSON;
21             }
22             }
23              
24             =for stopwords Hostname Str tcp ip unix
25              
26             =head1 NAME
27              
28             JSONRPC::Transport::TCP - Client component for TCP JSONRPC
29              
30             =head1 SYNOPSIS
31              
32             use JSONRPC::Transport::TCP;
33            
34             my $rpc = JSONRPC::Transport::TCP->new( host => '127.0.0.1', port => 3000 );
35             my $res = $rpc->call('echo', 'arg1', 'arg2' )
36             or die $rpc->error;
37            
38             print $res->result;
39              
40             =head1 DESCRIPTION
41              
42             This module is a simple client side implementation about JSONRPC via TCP.
43              
44             This module doesn't support continual tcp streams, and so it open/close connection on each request.
45              
46             =head1 METHODS
47              
48             =head2 new
49              
50             Create new client object.
51              
52             Parameters:
53              
54             =over
55              
56             =item host => 'Str'
57              
58             Hostname or ip address to connect.
59              
60             This should be set 'unix/' when you want to connect to unix socket.
61              
62             =item port => 'Int | Str'
63              
64             Port number or unix socket path to connect
65              
66             =back
67              
68             =cut
69              
70             sub new {
71 0 0   0 1   my $self = shift->SUPER::new( @_ > 1 ? {@_} : $_[0] );
72              
73 0           $self->{id} = 0;
74 0 0 0       $self->{json} ||= $XS_AVAILABLE ? JSON::XS->new->utf8 : JSON->new->utf8;
75 0   0       $self->{delimiter} ||= q[];
76              
77 0           $self;
78             }
79              
80             =head2 connect
81              
82             Connect remote host.
83              
84             This module automatically connect on following "call" method, so you have not to call this method.
85              
86             =cut
87              
88             sub connect {
89 0     0 1   my $self = shift;
90 0 0         my $params = @_ > 1 ? {@_} : $_[0];
91              
92 0 0         $self->disconnect if $self->{socket};
93              
94 0           my $socket;
95 0           eval {
96             # unix socket
97 0   0       my $host = $params->{host} || $self->{host};
98 0   0       my $port = $params->{port} || $self->{port};
99              
100 0 0         if ($host eq 'unix/') {
101 0 0 0       $socket = IO::Socket::UNIX->new(
102             Peer => $port,
103             Timeout => $self->{timeout} || 30,
104             ) or croak qq/Unable to connect to unix socket "$port": $!/;
105             }
106             else {
107 0 0 0       $socket = IO::Socket::INET->new(
108             PeerAddr => $host,
109             PeerPort => $port,
110             Proto => 'tcp',
111             Timeout => $self->{timeout} || 30,
112             )
113             or croak
114 0   0       qq/Unable to connect to "@{[ $params->{host} || $self->{host} ]}:@{[ $params->{port} || $self->{port} ]}": $!/;
  0   0        
115             }
116              
117 0           $socket->autoflush(1);
118              
119 0           $self->{socket} = $socket;
120             };
121 0 0         if ($@) {
122 0           $self->{error} = $@;
123 0           return;
124             }
125              
126 0           1;
127             }
128              
129             =head2 disconnect
130              
131             Disconnect the connection
132              
133             =cut
134              
135             sub disconnect {
136 0     0 1   my $self = shift;
137 0 0         delete $self->{socket} if $self->{socket};
138             }
139              
140             =head2 call($method_name, @params)
141              
142             Call remote method.
143              
144             When remote method is success, it returns self object that contains result as ->result accessor.
145              
146             If some error are occurred, it returns undef, and you can check the error by ->error accessor.
147              
148             Parameters:
149              
150             =over
151              
152             =item $method_name
153              
154             Remote method name to call
155              
156             =item @params
157              
158             Remote method parameters.
159              
160             =back
161              
162             =cut
163              
164             sub call {
165 0     0 1   my ($self, $method, @params) = @_;
166              
167 0 0         $self->connect unless $self->{socket};
168 0 0         return unless $self->{socket};
169              
170 0           my $request = {
171             id => ++$self->{id},
172             method => $method,
173             params => \@params,
174             };
175 0           $self->{socket}->print($self->{json}->encode($request) . $self->{delimiter});
176              
177 0           my $timeout = $self->{socket}->timeout;
178 0           my $limit = time + $timeout;
179              
180 0 0         my $select = IO::Select->new or croak $!;
181 0           $select->add($self->{socket});
182              
183 0           my $buf = '';
184              
185 0           while ($limit >= time) {
186 0 0         my @ready = $select->can_read( $limit - time )
187             or last;
188              
189 0           for my $s (@ready) {
190 0 0         croak qq/$s isn't $self->{socket}/ unless $s eq $self->{socket};
191             }
192              
193 0 0         unless (my $l = $self->{socket}->sysread( $buf, 512, length($buf) )) {
194 0           my $e = $!;
195 0           $self->disconnect;
196 0           croak qq/Error reading: $e/;
197             }
198              
199 0           my $json = eval { $self->{json}->incr_parse($buf) };
  0            
200              
201 0 0         if ($@) {
    0          
202 0           $self->{error} = $@;
203 0           $self->disconnect;
204 0           return;
205             }
206             elsif ($json) {
207 0 0         if ($json->{error}) {
208 0           $self->{error} = $json->{error};
209 0           $self->disconnect;
210 0           return;
211             }
212             else {
213 0           $self->{result} = $json->{result};
214 0           $self->disconnect;
215 0           return $self;
216             }
217             }
218             else {
219 0           $buf = '';
220 0           next;
221             }
222             }
223              
224 0           croak "request timeout";
225             }
226              
227             =head2 DESTROY
228              
229             Automatically disconnect when object destroy.
230              
231             =cut
232              
233             sub DESTROY {
234 0     0     my $self = shift;
235 0           $self->disconnect;
236             }
237              
238             =head1 ACCESSORS
239              
240             =head2 result
241              
242             Contains result of remote method
243              
244             =head2 error
245              
246             Contains error of remote method
247              
248             =head1 AUTHOR
249              
250             Daisuke Murase
251              
252             =head1 COPYRIGHT
253              
254             This program is free software; you can redistribute
255             it and/or modify it under the same terms as Perl itself.
256              
257             The full text of the license can be found in the
258             LICENSE file included with this module.
259              
260             =cut
261              
262             1;