File Coverage

blib/lib/AnyEvent/JSONRPC/HTTP/Client.pm
Criterion Covered Total %
statement 19 21 90.4
branch n/a
condition n/a
subroutine 7 7 100.0
pod n/a
total 26 28 92.8


line stmt bran cond sub pod time code
1             package AnyEvent::JSONRPC::HTTP::Client;
2 1     1   7 use Any::Moose;
  1         2  
  1         12  
3 1     1   655 use Any::Moose '::Util::TypeConstraints';
  1         2  
  1         4  
4              
5             extends 'AnyEvent::JSONRPC::Client';
6              
7 1     1   386 use Carp;
  1         2  
  1         97  
8 1     1   6 use Scalar::Util 'weaken';
  1         3  
  1         54  
9              
10 1     1   8 use AnyEvent;
  1         2  
  1         31  
11 1     1   1278 use AnyEvent::HTTP;
  1         9219  
  1         88  
12              
13 1     1   409 use JSON::RPC::Common::Procedure::Call;
  0            
  0            
14             use JSON::RPC::Common::Procedure::Return;
15              
16             use MIME::Base64;
17             use JSON::XS;
18              
19             has url => (
20             is => 'ro',
21             isa => 'Str',
22             required => 1,
23             );
24              
25             has version => (
26             is => 'rw',
27             isa => enum( [qw( 1.0 1.1 2.0 )] ),
28             default => "2.0",
29             );
30              
31             has username => (
32             is => "rw",
33             isa => 'Str',
34             predicate => "has_username"
35             );
36              
37             has password => (
38             is => "rw",
39             isa => "Str"
40             );
41              
42             has _request_pool => (
43             is => 'ro',
44             isa => 'HashRef',
45             lazy => 1,
46             default => sub { {} },
47             );
48              
49             has _next_id => (
50             is => 'ro',
51             isa => 'CodeRef',
52             lazy => 1,
53             default => sub {
54             my $id = 0;
55             sub { ++$id };
56             },
57             );
58              
59             no Any::Moose;
60              
61             sub call {
62             my ($self, $method, @params) = @_;
63              
64             my $request = JSON::RPC::Common::Procedure::Call->inflate (
65             version => $self->version,
66             id => $self->_next_id->(),
67             method => $method,
68             params => $self->_params( @params ),
69             );
70              
71             my $guard = http_post $self->url,
72             encode_json( $request->deflate ) . " ",
73             headers => {
74             "Content-Type" => "application/json",
75             $self->_authorization_header,
76             },
77             sub { $self->_handle_response( @_ ) };
78            
79             my $cv = AnyEvent->condvar;
80              
81             $self->_request_pool->{ $request->id } = [ $guard, $cv ];
82              
83             return $cv;
84             }
85              
86             sub _authorization_header {
87             my $self = shift;
88              
89             return unless $self->has_username;
90              
91             return Authorization => "Basic " . encode_base64( $self->username . ":" . $self->password );
92             }
93              
94             sub _handle_response {
95             my ($self, $json, $header) = @_;
96              
97             unless ( $header->{Status} =~ /^2/) {
98             warn qq/Invalid response from server: $header->{Status} $header->{Reason}/;
99             return;
100             }
101              
102             my $response = JSON::RPC::Common::Procedure::Return->inflate( decode_json $json );
103             my $d = delete $self->_request_pool->{ $response->id };
104             unless ($d) {
105             warn q/Invalid response from server/;
106             return;
107             }
108              
109             if (my $error = $response->error) {
110             $d->[1]->croak($error);
111             }
112             else {
113             $d->[1]->send($response->result);
114             }
115             }
116              
117             sub notify {
118             my ($self, $method, @params) = @_;
119              
120             my $request = JSON::RPC::Common::Call->inflate (
121             version => $self->version,
122             method => $method,
123             params => $self->_params( @params ),
124             );
125              
126             http_post $self->url,
127             encode_json( $request->deflate ),
128             headers => {
129             "Content-Type" => "application/json",
130             $self->_authorization_header,
131             },
132             sub { 1; };
133             }
134              
135             sub _params {
136             my $self = shift;
137              
138             my $param;
139             if (scalar( @_ ) == 1) {
140             $param = shift;
141            
142             $param = [ $param ] if (ref $param eq "HASH" and $self->version eq "1.0")
143             || not ref $param;
144            
145             } else {
146             $param = [ @_ ];
147             }
148              
149             return $param;
150             }
151              
152             __PACKAGE__->meta->make_immutable;
153              
154             __END__
155              
156             =encoding utf-8
157              
158             =begin stopwords
159              
160             AnyEvent Coro JSONRPC Hostname Str HTTP HTTP-based
161             blockingly condvar condvars coroutine unix
162              
163             =end stopwords
164              
165             =head1 NAME
166              
167             AnyEvent::JSONRPC::HTTP::Client - Simple HTTP-based JSONRPC client
168              
169             =head1 SYNOPSIS
170              
171             use AnyEvent::JSONRPC::HTTP::Client;
172            
173             my $client = AnyEvent::JSONRPC::HTTP::Client->new(
174             url => 'http://rpc.example.net/issues',
175             username => "pmakholm",
176             password => "secret",
177             );
178            
179             # blocking interface
180             my $res = $client->call( echo => 'foo bar' )->recv; # => 'foo bar';
181            
182             # non-blocking interface
183             $client->call( echo => 'foo bar' )->cb(sub {
184             my $res = $_[0]->recv; # => 'foo bar';
185             });
186              
187             =head1 DESCRIPTION
188              
189             This module is the HTTP client part of L<AnyEvent::JSONRPC>.
190              
191             =head2 AnyEvent condvars
192              
193             The main thing you have to remember is that all the data retrieval methods
194             return an AnyEvent condvar, C<$cv>. If you want the actual data from the
195             request, there are a few things you can do.
196              
197             You may have noticed that many of the examples in the SYNOPSIS call C<recv>
198             on the condvar. You're allowed to do this under 2 circumstances:
199              
200             =over 4
201              
202             =item Either you're in a main program,
203              
204             Main programs are "allowed to call C<recv> blockingly", according to the
205             author of L<AnyEvent>.
206              
207             =item or you're in a Coro + AnyEvent environment.
208              
209             When you call C<recv> inside a coroutine, only that coroutine is blocked
210             while other coroutines remain active. Thus, the program as a whole is
211             still responsive.
212              
213             =back
214              
215             If you're not using Coro, and you don't want your whole program to block,
216             what you should do is call C<cb> on the condvar, and give it a coderef to
217             execute when the results come back. The coderef will be given a condvar
218             as a parameter, and it can call C<recv> on it to get the data. The final
219             example in the SYNOPSIS gives a brief example of this.
220              
221             Also note that C<recv> will throw an exception if the request fails, so be
222             prepared to catch exceptions where appropriate.
223              
224             Please read the L<AnyEvent> documentation for more information on the proper
225             use of condvars.
226              
227             =head1 METHODS
228              
229             =head2 new (%options)
230              
231             Create new client object and return it.
232              
233             my $client = AnyEvent::JSONRPC::HTTP::Client->new(
234             host => '127.0.0.1',
235             port => 4423,
236             %options,
237             );
238              
239             Available options are:
240              
241             =over 4
242              
243             =item url => 'Str'
244              
245             URL to json-RPC endpoint to connect. (Required)
246              
247             =item username => 'Str'
248              
249             Username to use for authorization (Optional).
250              
251             If this is set an Authorization header containing basic auth credential is
252             always sent with request.
253              
254             =item password => 'Str'
255              
256             Password used for authorization (optional)
257              
258             =back
259              
260             =head2 call ($method, @params)
261              
262             Call remote method named C<$method> with parameters C<@params>. And return condvar object for response.
263              
264             my $cv = $client->call( echo => 'Hello!' );
265             my $res = $cv->recv;
266              
267             If server returns an error, C<< $cv->recv >> causes croak by using C<< $cv->croak >>. So you can handle this like following:
268              
269             my $res;
270             eval { $res = $cv->recv };
271            
272             if (my $error = $@) {
273             # ...
274             }
275              
276             =head2 notify ($method, @params)
277              
278             Same as call method, but not handle response. This method just notify to server.
279              
280             $client->notify( echo => 'Hello' );
281              
282             =head1 AUTHOR
283              
284             Peter Makholm <peter@makholm.net>
285              
286             =head1 COPYRIGHT AND LICENSE
287              
288             Copyright (c) 2010 by Peter Makholm.
289              
290             This program is free software; you can redistribute
291             it and/or modify it under the same terms as Perl itself.
292              
293             The full text of the license can be found in the
294             LICENSE file included with this module.
295              
296             =cut