File Coverage

blib/lib/AnyEvent/JSONRPC/TCP/Client.pm
Criterion Covered Total %
statement 22 24 91.6
branch n/a
condition n/a
subroutine 8 8 100.0
pod n/a
total 30 32 93.7


line stmt bran cond sub pod time code
1             package AnyEvent::JSONRPC::TCP::Client;
2 8     8   8739 use Any::Moose;
  8         281945  
  8         112  
3 8     8   4683 use Any::Moose '::Util::TypeConstraints';
  8         19  
  8         44  
4              
5             extends 'AnyEvent::JSONRPC::Client';
6              
7 8     8   1949 use Carp;
  8         19  
  8         666  
8 8     8   46 use Scalar::Util 'weaken';
  8         27  
  8         424  
9              
10 8     8   11518 use AnyEvent;
  8         43064  
  8         265  
11 8     8   7166 use AnyEvent::Socket;
  8         71648  
  8         1356  
12 8     8   11782 use AnyEvent::Handle;
  8         74729  
  8         407  
13              
14 8     8   3772 use JSON::RPC::Common::Procedure::Call;
  0            
  0            
15             use JSON::RPC::Common::Procedure::Return;
16              
17             has host => (
18             is => 'ro',
19             isa => 'Str',
20             required => 1,
21             );
22              
23             has port => (
24             is => 'ro',
25             isa => 'Int|Str',
26             required => 1,
27             );
28              
29             has handler => (
30             is => 'rw',
31             isa => 'AnyEvent::Handle',
32             );
33              
34             has on_error => (
35             is => 'rw',
36             isa => 'CodeRef',
37             lazy => 1,
38             default => sub {
39             return sub {
40             my ($handle, $fatal, $message) = @_;
41             croak sprintf "Client got error: %s", $message;
42             };
43             },
44             );
45              
46             has version => (
47             is => 'rw',
48             isa => enum( [qw( 1.0 1.1 2.0 )] ),
49             default => "2.0",
50             );
51              
52             has handler_options => (
53             is => 'ro',
54             isa => 'HashRef',
55             default => sub { {} },
56             );
57              
58             has _request_pool => (
59             is => 'ro',
60             isa => 'ArrayRef',
61             lazy => 1,
62             default => sub { [] },
63             );
64              
65             has _next_id => (
66             is => 'ro',
67             isa => 'CodeRef',
68             lazy => 1,
69             default => sub {
70             my $id = 0;
71             sub { ++$id };
72             },
73             );
74              
75             has _callbacks => (
76             is => 'ro',
77             isa => 'HashRef',
78             lazy => 1,
79             default => sub { {} },
80             );
81              
82             has _connection_guard => (
83             is => 'rw',
84             isa => 'Object',
85             );
86              
87             no Any::Moose;
88              
89             sub BUILD {
90             my $self = shift;
91              
92             my $guard = tcp_connect $self->host, $self->port, sub {
93             my ($fh) = @_
94             or return
95             $self->on_error->(
96             undef, 1,
97             "Failed to connect $self->{host}:$self->{port}: $!",
98             );
99              
100             my $handle = AnyEvent::Handle->new(
101             on_error => sub {
102             my ($h, $fatal, $msg) = @_;
103             $self->on_error->(@_);
104             $h->destroy;
105             },
106             %{ $self->handler_options },
107             fh => $fh,
108             );
109              
110             $handle->on_read(sub {
111             shift->unshift_read(json => sub {
112             $self->_handle_response( $_[1] );
113             });
114             });
115              
116             while (my $pooled = shift @{ $self->_request_pool }) {
117             $handle->push_write( json => $pooled->deflate );
118             }
119              
120             $self->handler( $handle );
121             };
122             weaken $self;
123              
124             $self->_connection_guard($guard);
125             }
126              
127             sub call {
128             my ($self, $method, @params) = @_;
129              
130             my $request = JSON::RPC::Common::Procedure::Call->inflate (
131             version => $self->version,
132             id => $self->_next_id->(),
133             method => $method,
134             params => $self->_params( @params ),
135             );
136              
137             if ($self->handler) {
138             my $json = $request->deflate;
139             $self->handler->push_write( json => $json );
140             }
141             else {
142             push @{ $self->_request_pool }, $request;
143             }
144              
145             $self->_callbacks->{ $request->id } = AnyEvent->condvar;
146             }
147              
148             sub _handle_response {
149             my ($self, $json) = @_;
150              
151             my $response = JSON::RPC::Common::Procedure::Return->inflate( $json );
152             my $d = delete $self->_callbacks->{ $response->id };
153             unless ($d) {
154             warn q/Invalid response from server/;
155             return;
156             }
157              
158             if (my $error = $response->error) {
159             $d->croak($error);
160             }
161             else {
162             $d->send($response->result);
163             }
164             }
165              
166             sub notify {
167             my ($self, $method, @params) = @_;
168              
169             my $request = JSON::RPC::Common::Call->inflate (
170             version => $self->version,
171             method => $method,
172             params => $self->_params( @params ),
173             );
174              
175             if ($self->handler) {
176             $self->handler->push_write( json => $request->deflate );
177             }
178             else {
179             push @{ $self->_request_pool }, $request;
180             }
181             }
182              
183             sub _params {
184             my $self = shift;
185              
186             my $param;
187             if (scalar( @_ ) == 1) {
188             $param = shift;
189            
190             $param = [ $param ] if (ref $param eq "HASH" and $self->version eq "1.0")
191             || not ref $param;
192            
193             } else {
194             $param = [ @_ ];
195             }
196              
197             return $param;
198             }
199              
200             __PACKAGE__->meta->make_immutable;
201              
202             __END__
203              
204             =encoding utf-8
205              
206             =begin stopwords
207              
208             AnyEvent Coro JSONRPC Hostname Str TCP TCP-based
209             blockingly condvar condvars coroutine unix
210              
211             =end stopwords
212              
213             =head1 NAME
214              
215             AnyEvent::JSONRPC::TCP::Client - Simple TCP-based JSONRPC client
216              
217             =head1 SYNOPSIS
218              
219             use AnyEvent::JSONRPC::TCP::Client;
220            
221             my $client = AnyEvent::JSONRPC::TCP::Client->new(
222             host => '127.0.0.1',
223             port => 4423,
224             );
225            
226             # blocking interface
227             my $res = $client->call( echo => 'foo bar' )->recv; # => 'foo bar';
228            
229             # non-blocking interface
230             $client->call( echo => 'foo bar' )->cb(sub {
231             my $res = $_[0]->recv; # => 'foo bar';
232             });
233              
234             =head1 DESCRIPTION
235              
236             This module is client part of L<AnyEvent::JSONRPC>.
237              
238             =head2 AnyEvent condvars
239              
240             The main thing you have to remember is that all the data retrieval methods
241             return an AnyEvent condvar, C<$cv>. If you want the actual data from the
242             request, there are a few things you can do.
243              
244             You may have noticed that many of the examples in the SYNOPSIS call C<recv>
245             on the condvar. You're allowed to do this under 2 circumstances:
246              
247             =over 4
248              
249             =item Either you're in a main program,
250              
251             Main programs are "allowed to call C<recv> blockingly", according to the
252             author of L<AnyEvent>.
253              
254             =item or you're in a Coro + AnyEvent environment.
255              
256             When you call C<recv> inside a coroutine, only that coroutine is blocked
257             while other coroutines remain active. Thus, the program as a whole is
258             still responsive.
259              
260             =back
261              
262             If you're not using Coro, and you don't want your whole program to block,
263             what you should do is call C<cb> on the condvar, and give it a coderef to
264             execute when the results come back. The coderef will be given a condvar
265             as a parameter, and it can call C<recv> on it to get the data. The final
266             example in the SYNOPSIS gives a brief example of this.
267              
268             Also note that C<recv> will throw an exception if the request fails, so be
269             prepared to catch exceptions where appropriate.
270              
271             Please read the L<AnyEvent> documentation for more information on the proper
272             use of condvars.
273              
274             =head1 METHODS
275              
276             =head2 new (%options)
277              
278             Create new client object and return it.
279              
280             my $client = AnyEvent::JSONRPC::TCP::Client->new(
281             host => '127.0.0.1',
282             port => 4423,
283             %options,
284             );
285              
286             Available options are:
287              
288             =over 4
289              
290             =item host => 'Str'
291              
292             Hostname to connect. (Required)
293              
294             You should set this option to "unix/" if you will set unix socket to port option.
295              
296             =item port => 'Int | Str'
297              
298             Port number or unix socket path to connect. (Required)
299              
300             =item on_error => $cb->($handle, $fatal, $message)
301              
302             Error callback code reference, which is called when some error occurred.
303             This has same arguments as L<AnyEvent::Handle>, and also act as handler's on_error callback.
304              
305             Default is just croak.
306              
307             If you want to set other options to handle object, use handler_options option showed below.
308              
309             =item handler_options => 'HashRef'
310              
311             This is passed to constructor of L<AnyEvent::Handle> that is used manage connection.
312              
313             Default is empty.
314              
315             =back
316              
317             =head2 call ($method, @params)
318              
319             Call remote method named C<$method> with parameters C<@params>. And return condvar object for response.
320              
321             my $cv = $client->call( echo => 'Hello!' );
322             my $res = $cv->recv;
323              
324             If server returns an error, C<< $cv->recv >> causes croak by using C<< $cv->croak >>. So you can handle this like following:
325              
326             my $res;
327             eval { $res = $cv->recv };
328            
329             if (my $error = $@) {
330             # ...
331             }
332              
333             =head2 notify ($method, @params)
334              
335             Same as call method, but not handle response. This method just notify to server.
336              
337             $client->notify( echo => 'Hello' );
338              
339             =head1 AUTHOR
340              
341             Daisuke Murase <typester@cpan.org>
342              
343             =head1 COPYRIGHT AND LICENSE
344              
345             Copyright (c) 2009 by KAYAC Inc.
346              
347             This program is free software; you can redistribute
348             it and/or modify it under the same terms as Perl itself.
349              
350             The full text of the license can be found in the
351             LICENSE file included with this module.
352              
353             =cut