File Coverage

blib/lib/Lim/RPC/Client.pm
Criterion Covered Total %
statement 42 189 22.2
branch 0 82 0.0
condition 0 15 0.0
subroutine 14 19 73.6
pod 3 3 100.0
total 59 308 19.1


line stmt bran cond sub pod time code
1             package Lim::RPC::Client;
2              
3 7     7   41 use common::sense;
  7         15  
  7         73  
4 7     7   352 use Carp;
  7         17  
  7         529  
5              
6 7     7   80 use Log::Log4perl ();
  7         12  
  7         148  
7 7     7   38 use Scalar::Util qw(blessed weaken);
  7         11  
  7         804  
8              
9 7     7   47 use AnyEvent ();
  7         12  
  7         106  
10 7     7   11490 use AnyEvent::Socket ();
  7         166466  
  7         335  
11 7     7   23477 use AnyEvent::Handle ();
  7         84045  
  7         266  
12              
13 7     7   8276 use HTTP::Request ();
  7         162433  
  7         259  
14 7     7   7782 use HTTP::Response ();
  7         59679  
  7         256  
15 7     7   78 use HTTP::Status qw(:constants);
  7         17  
  7         4985  
16              
17 7     7   9514 use JSON::XS ();
  7         57275  
  7         205  
18              
19 7     7   74 use Lim ();
  7         20  
  7         128  
20 7     7   52 use Lim::Error ();
  7         16  
  7         122  
21 7     7   5214 use Lim::RPC::TLS ();
  7         33  
  7         24247  
22              
23             =encoding utf8
24              
25             =head1 NAME
26              
27             ...
28              
29             =head1 VERSION
30              
31             See L for version.
32              
33             =over 4
34              
35             =item OK
36              
37             =item ERROR
38              
39             =item MAX_RESPONSE_LEN
40              
41             =back
42              
43             =cut
44              
45             our $VERSION = $Lim::VERSION;
46             our $JSON = JSON::XS->new->ascii;
47              
48             sub OK (){ 1 }
49             sub ERROR (){ -1 }
50              
51             sub MAX_RESPONSE_LEN (){ 8 * 1024 * 1024 }
52              
53             =head1 SYNOPSIS
54              
55             ...
56              
57             =head1 SUBROUTINES/METHODS
58              
59             =head2 new
60              
61             =cut
62              
63             sub new {
64 0     0 1   my $this = shift;
65 0   0       my $class = ref($this) || $this;
66 0           my %args = ( @_ );
67 0           my $self = {
68             logger => Log::Log4perl->get_logger,
69             rbuf => '',
70             status => 0,
71             error => ''
72             };
73 0           bless $self, $class;
74 0           my $real_self = $self;
75 0           weaken($self);
76            
77 0 0         unless (defined $args{host}) {
78 0           confess __PACKAGE__, ': No host specified';
79             }
80 0 0         unless (defined $args{port}) {
81 0           confess __PACKAGE__, ': No port specified';
82             }
83 0 0         unless (defined $args{method}) {
84 0           confess __PACKAGE__, ': No method specified';
85             }
86 0 0         unless (defined $args{uri}) {
87 0           confess __PACKAGE__, ': No uri specified';
88             }
89 0 0 0       if (defined $args{data} and ref($args{data}) ne 'HASH') {
90 0           confess __PACKAGE__, ': Data is not a hash';
91             }
92              
93 0 0         if (!defined Lim::RPC::TLS->instance->tls_ctx) {
94 0           confess 'using HTTPS but can not create TLS context';
95             }
96            
97 0           $self->{host} = $args{host};
98 0           $self->{port} = $args{port};
99 0           $self->{uri} = $args{uri};
100 0 0 0       if (defined $args{cb} and ref($args{cb}) eq 'CODE') {
101 0           $self->{cb} = $args{cb};
102             }
103 0           $self->{request} = HTTP::Request->new($args{method}, $self->{uri});
104 0           $self->{request}->protocol('HTTP/1.1');
105 0 0         if (defined $args{data}) {
106 0           my $json;
107 0           eval {
108 0           $json = $JSON->encode($args{data});
109             };
110 0 0         if ($@) {
111 0           $self->{status} = ERROR;
112 0           $self->{error} = $@;
113            
114 0 0         if (exists $self->{cb}) {
115 0           $self->{cb}->($self);
116 0           delete $self->{cb};
117             }
118 0           return;
119             }
120 0           $self->{request}->content($json);
121 0           $self->{request}->header('Content-Length' => length($json));
122 0           $self->{request}->header('Content-Type' => 'application/json');
123             }
124             else {
125 0           $self->{request}->header('Content-Length' => 0);
126             }
127              
128             $self->{socket} = AnyEvent::Socket::tcp_connect $self->{host}, $self->{port}, sub {
129 0     0     my ($fh, $host, $port) = @_;
130              
131 0 0         unless (defined $self) {
132 0           return;
133             }
134            
135 0 0         unless (defined $fh) {
136 0 0         Lim::WARN and $self->{logger}->warn('Error: ', $!);
137 0           $self->{status} = ERROR;
138 0           $self->{error} = $!;
139            
140 0 0         if (exists $self->{cb}) {
141 0           $self->{cb}->($self);
142 0           delete $self->{cb};
143             }
144 0           return;
145             }
146            
147 0           my $handle;
148             $handle = AnyEvent::Handle->new(
149             fh => $fh,
150             tls => 'connect',
151             tls_ctx => Lim::RPC::TLS->instance->tls_ctx,
152             timeout => Lim::Config->{rpc}->{timeout},
153             on_error => sub {
154 0           my ($handle, $fatal, $message) = @_;
155              
156 0 0         unless (defined $self) {
157 0           return;
158             }
159            
160 0 0         Lim::WARN and $self->{logger}->warn($handle, ' Error: ', $message);
161 0           $self->{status} = ERROR;
162 0           $self->{error} = $message;
163            
164 0 0         if (exists $self->{cb}) {
165 0           $self->{cb}->($self, Lim::Error->new(
166             message => $self->{error},
167             module => $self
168             ));
169 0           delete $self->{cb};
170             }
171 0           $handle->destroy;
172             },
173             on_timeout => sub {
174 0           my ($handle) = @_;
175            
176 0 0         unless (defined $self) {
177 0           return;
178             }
179              
180 0 0         Lim::WARN and $self->{logger}->warn($handle, ' TIMEOUT');
181 0           $self->{status} = ERROR;
182 0           $self->{error} = 'Connection/Request/Response Timeout';
183            
184 0 0         if (exists $self->{cb}) {
185 0           $self->{cb}->($self, Lim::Error->new(
186             code => HTTP_REQUEST_TIMEOUT,
187             message => $self->{error},
188             module => $self
189             ));
190 0           delete $self->{cb};
191             }
192 0           $handle->destroy;
193             },
194             on_eof => sub {
195 0           my ($handle) = @_;
196            
197 0 0         unless (defined $self) {
198 0           return;
199             }
200              
201 0 0         Lim::WARN and $self->{logger}->warn($handle, ' EOF');
202            
203 0 0         if (exists $self->{cb}) {
204 0           $self->{cb}->($self);
205 0           delete $self->{cb};
206             }
207 0           $handle->destroy;
208             },
209             on_read => sub {
210 0           my ($handle) = @_;
211            
212 0 0         unless (defined $self) {
213 0           return;
214             }
215              
216 0 0         if ((length($self->{rbuf}) + length($handle->{rbuf})) > MAX_RESPONSE_LEN) {
217 0 0         if (exists $self->{on_error}) {
218 0           $self->{on_error}->($self, 1, 'Response too long');
219             }
220 0           $handle->push_shutdown;
221 0           $handle->destroy;
222 0           return;
223             }
224            
225 0 0         unless (exists $self->{content}) {
226 0           $self->{headers} .= $handle->{rbuf};
227            
228 0 0         if ($self->{headers} =~ /\015?\012\015?\012/o) {
229 0           my ($headers, $content) = split(/\015?\012\015?\012/o, $self->{headers}, 2);
230 0           $self->{headers} = $headers;
231 0           $self->{content} = $content;
232 0           $self->{response} = HTTP::Response->parse($self->{headers});
233             }
234             }
235             else {
236 0           $self->{content} .= $handle->{rbuf};
237             }
238 0           $handle->{rbuf} = '';
239            
240 0 0 0       if (defined $self->{response} and length($self->{content}) == $self->{response}->header('Content-Length')) {
241 0           my $response = $self->{response};
242 0           $response->content($self->{content});
243 0           delete $self->{response};
244 0           delete $self->{content};
245 0           $self->{headers} = '';
246            
247 0           my $data;
248            
249 0 0         if ($response->code == 200) {
250 0           $self->{status} = OK;
251             }
252             else {
253 0           $self->{status} = ERROR;
254             }
255              
256 0 0         if ($response->header('Content-Length')) {
257 0 0         if ($response->header('Content-Type') =~ /application\/json/io) {
258 0           eval {
259 0           $data = $JSON->decode($response->decoded_content);
260             };
261 0 0         if ($@) {
262 0           $self->{status} = ERROR;
263 0           $self->{error} = $@;
264 0           undef($data);
265             }
266             else {
267 0 0         if (ref($data) ne 'HASH') {
    0          
268 0           $data = Lim::Error->new(
269             code => 500,
270             message => 'Invalid data returned, not a hash',
271             module => $self);
272 0           $self->{status} = ERROR;
273             }
274             elsif ($self->{status} == ERROR) {
275 0           $data = Lim::Error->new->set($data);
276             }
277             }
278             }
279             else {
280 0           $self->{status} = ERROR;
281 0           $self->{error} = 'Unknown content type ['.$response->header('Content-Type').'] returned';
282             }
283             }
284            
285 0 0         if ($self->{status} == ERROR) {
286 0 0         unless (defined $data) {
287 0           $data = Lim::Error->new(
288             code => $response->code,
289             message => $self->{error},
290             module => $self);
291             }
292 0 0 0       unless (blessed $data and $data->isa('Lim::Error')) {
293 0           confess __PACKAGE__, ': status is ERROR but data is not a Lim::Error object';
294             }
295             }
296              
297 0 0         if (exists $self->{cb}) {
298 0           $self->{cb}->($self, $data);
299 0           delete $self->{cb};
300             }
301 0           $handle->push_shutdown;
302 0           $handle->destroy;
303             }
304 0           });
305            
306 0           $self->{handle} = $handle;
307 0           $handle->push_write($self->{request}->as_string("\015\012"));
308 0           delete $self->{request};
309 0           };
310              
311 0 0         Lim::OBJ_DEBUG and $self->{logger}->debug('new ', __PACKAGE__, ' ', $self);
312 0           $real_self;
313             }
314              
315             sub DESTROY {
316 0     0     my ($self) = @_;
317 0 0         Lim::OBJ_DEBUG and $self->{logger}->debug('destroy ', __PACKAGE__, ' ', $self);
318            
319 0           delete $self->{client};
320 0           delete $self->{socket};
321 0           delete $self->{handle};
322             }
323              
324             =head2 status
325              
326             =cut
327              
328             sub status {
329 0     0 1   $_[0]->{status};
330             }
331              
332             =head2 error
333              
334             =cut
335              
336             sub error {
337 0     0 1   $_[0]->{error};
338             }
339              
340             =head1 AUTHOR
341              
342             Jerry Lundström, C<< >>
343              
344             =head1 BUGS
345              
346             Please report any bugs or feature requests to L.
347              
348             =head1 SUPPORT
349              
350             You can find documentation for this module with the perldoc command.
351              
352             perldoc Lim
353              
354             You can also look for information at:
355              
356             =over 4
357              
358             =item * Lim issue tracker (report bugs here)
359              
360             L
361              
362             =back
363              
364             =head1 ACKNOWLEDGEMENTS
365              
366             =head1 LICENSE AND COPYRIGHT
367              
368             Copyright 2012-2013 Jerry Lundström.
369              
370             This program is free software; you can redistribute it and/or modify it
371             under the terms of either: the GNU General Public License as published
372             by the Free Software Foundation; or the Artistic License.
373              
374             See http://dev.perl.org/licenses/ for more information.
375              
376              
377             =cut
378              
379             1; # End of Lim::RPC::Client