File Coverage

blib/lib/HTTP/MultiGet/Role.pm
Criterion Covered Total %
statement 98 115 85.2
branch 21 34 61.7
condition 4 6 66.6
subroutine 24 26 92.3
pod 8 11 72.7
total 155 192 80.7


line stmt bran cond sub pod time code
1             package HTTP::MultiGet::Role;
2              
3 2     2   1212017 use Modern::Perl;
  2         17  
  2         13  
4 2     2   270 use Moo::Role;
  2         11  
  2         14  
5 2     2   1775 use MooX::Types::MooseLike::Base qw(:all);
  2         13126  
  2         702  
6 2     2   1056 use AnyEvent::HTTP::MultiGet;
  2         6  
  2         68  
7 2     2   19 use AnyEvent;
  2         4  
  2         53  
8 2     2   1539 use JSON qw();
  2         16517  
  2         58  
9 2     2   15 use Data::Dumper;
  2         5  
  2         118  
10 2     2   14 use Carp qw(croak);
  2         4  
  2         94  
11 2     2   12 use namespace::clean;
  2         4  
  2         17  
12 2     2   1623 use Ref::Util qw(is_plain_arrayref);
  2         3  
  2         128  
13              
14             BEGIN {
15 2     2   15 with 'Log::LogMethods','Data::Result::Moo';
16             }
17              
18             our $AUTOLOAD;
19              
20             =head1 NAME
21              
22             HTTP::MultiGet::Role - Role for building blocking/non-blocking AnyEvent friendly REST Clients
23              
24             =head1 SYNOPSIS
25              
26             package My::Rest::Class;
27              
28             use Modern::Perl;
29             use Moo;
30             BEGIN { with 'HTTP::MultiGet::Role' }
31              
32             sub que_some_request {
33             my ($self,$cb)=@_;
34             my $request=HTTP::Request->new(GET=>'https://some_json_endpoint');
35             return $self->queue_request($request,$cb);
36             }
37              
38              
39             Blocking Example
40              
41             # blocking context
42             use My::Rest::Class;
43              
44             my $self=new My::Rest::Class;
45             my $result=$self->some_request;
46             die $result unless $result;
47              
48              
49             NonBlocking Example
50              
51             # non blocking
52             use AnyEvent::Loop;
53             use My::Rest::Class;
54              
55             my $self=new My::Rest::Class;
56             my $id=$self->some_request(sub {
57             my ($self,$id,$result,$request,$response)=@_;
58             });
59              
60             $obj->agent->run_next;
61             AnyEvent::Loop::run;
62              
63             =head1 DESCRIPTION
64              
65             In the real world we are often confronted with a situation of needing and or wanting blocking and non-blocking code, but we normally only have time to develop one or the other. This class provided an AnyEvent friendly framework that solves some of the issues involved in creating both with 1 code base.
66              
67             The solution presented by this module is to simply develop the non blocking interface and dynamically AUTOLOAD the blocking interface as needed. One of the major advantages of this model of coding is it becomes possible to create asyncronous calls in what looks like syncronous code.
68              
69             More documentation comming soon.. time permitting.
70              
71             =cut
72              
73             our %MULTIGET_ARRGS=(
74             timeout=>300,
75             max_que_count=>20,
76             );
77              
78             our $VERSION=$HTTP::MultiGet::VERSION;
79              
80             =head1 OO Declarations
81              
82             This section documents the Object Declarations. ALl of these arguments are optional and autogenerated on demand if not passed into the constructor.
83              
84             agnet: AnyEvent::HTTP::MultiGet object
85             json: JSON object
86              
87             Run Time State Settings ( modify at your own risk!! )
88              
89             is_blocking: Boolean ( denotes if we are in a blocking context or not )
90             block_for_more: array ref of additoinal ids to block for in a blocking context
91             pending: hash ref that outbound request objects
92             result_map: hash ref that contains the inbound result objects
93             jobs: anonymous hash, used to keep our results that never hit IO
94              
95             Success Range for parsing json
96              
97             As of version 1.017 a range of codes can now be set to validate if the response should be parsed as json
98              
99             code_parse_start: 199 # if the response code is greater than
100             code_parse_end: 300 # if the response code is less than
101              
102             =cut
103              
104             has agent=>(
105             is=>'ro',
106             isa=>Object,
107             required=>1,
108             default=>sub {
109             new AnyEvent::HTTP::MultiGet(%MULTIGET_ARRGS)
110             },
111             lazy=>1,
112             );
113              
114              
115             has jobs=>(
116             is=>'ro',
117             default=>sub { {} },
118             lazy=>1,
119             );
120              
121             has is_blocking=>(
122             is=>'rw',
123             isa=>Bool,
124             default=>0,
125             lazy=>1,
126             );
127              
128             has block_for_more=>(
129             is=>'rw',
130             isa=>ArrayRef,
131             default=>sub { [] },
132             lazy=>1,
133             );
134              
135             has json =>(
136             is=>'ro',
137             isa=>Object,
138             required=>1,
139             lazy=>1,
140             default=>sub {
141             my $json=JSON->new->allow_nonref(&JSON::true)->utf8->relaxed(&JSON::true);
142             return $json;
143             },
144             );
145              
146             has pending=>(
147             is=>'ro',
148             isa=>HashRef,
149             required=>1,
150             default=>sub { {} },
151             lazy=>1,
152             );
153              
154             has result_map=>(
155             is=>'ro',
156             isa=>HashRef,
157             required=>1,
158             default=>sub { {} },
159             lazy=>1,
160             );
161              
162             has code_parse_start=>(
163             is=>'rw',
164             isa=>Int,
165             default=>199
166             );
167              
168             has code_parse_end=>(
169             is=>'rw',
170             isa=>Int,
171             default=>300
172             );
173              
174             =head1 OO Methods
175              
176             =over 4
177              
178             =item * my $result=$self->new_true({qw( some data )});
179              
180             Returns a new true Data::Result object.
181              
182             =item * my $result=$self->new_false("why this failed")
183              
184             Returns a new false Data::Result object
185              
186             =item * my $code=$self->cb;
187              
188             Internal object used to construct the global callback used for all http responses. You may need to overload this method in your own class.
189              
190             =cut
191              
192             sub cb {
193 2     2 1 24 my ($self)=@_;
194 2 100       10 return $self->{cb} if exists $self->{cb};
195             my $code=sub {
196 2     2   7 my ($mg,$ref,$response)=@_;
197 2 50       6 my $request=is_plain_arrayref($ref) ? $ref->[0] : $ref;
198 2 50       35 unless(exists $self->pending->{$request}) {
199              
200 0         0 $self->log_error("Request wasn't found!");
201 0         0 croak "Request Object wasn't found!";
202             }
203 2         19 my ($id,$cb)=@{delete $self->pending->{$request}};
  2         33  
204 2         23 my $result=$self->parse_response($request,$response);
205 2         450 $cb->($self,$id,$result,$request,$response);
206 1         7 };
207 1         3 $self->{cb}=$code;
208 1         7 return $code;
209             }
210              
211             =item * my $result=$self->parse_response($request,$response);
212              
213             Returns a Data::Result object, if true it contains the parsed result object, if false it contains why it failed. If you are doing anything other than parsing json on a 200 to 299 response you will need to overload this method.
214              
215             =cut
216              
217             sub parse_response {
218 309     309 1 373914 my ($self,$request,$response)=@_;
219              
220 309         837 my $content=$response->decoded_content;
221 309 50       34051 $content='' unless defined($content);
222 309 100 100     753 if($response->code >$self->code_parse_start && $response->code <$self->code_parse_end) {
223 301 50 33     19653 if(length($content)!=0 and $content=~ /^\s*[\[\{\"]/s) {
224 301         530 my $data=eval {$self->json->decode($content)};
  301         4877  
225 301 50       3625 if($@) {
226 0         0 return $self->new_false("Code: [".$response->code."] JSON Decode error [$@] Content: $content");
227             } else {
228 301         905 return $self->new_true($data);
229             }
230             } else {
231 0         0 return $self->new_true($content,$response);
232             }
233             } else {
234 8         467 return $self->new_false("Code: [".$response->code."] http error [".$response->status_line."] Content: $content");
235             }
236             }
237              
238             =item * my $id=$self->queue_request($request,$cb|undef);
239              
240             Returns an Id for the qued request. If $cb is undef then the default internal blocking callback is used.
241              
242             =cut
243              
244             sub queue_request {
245 2     2 1 7471 my ($self,$request,$cb)=@_;
246 2 50       8 $cb=$self->get_block_cb unless defined($cb);
247 2         47 my $id=$self->agent->add_cb($request,$self->cb);
248 2 50       8 my $req=is_plain_arrayref($request) ? $request->[0] : $request;
249 2         43 $self->pending->{$req}=[$id,$cb];
250 2         62 return $id;
251             }
252              
253             =item * my $id=$self->queue_result($cb,$result);
254              
255             Alows for result objects to look like they were placed in the the job que but wern't.
256              
257             Call back example
258              
259             sub {
260             my ($self,$id,$result,undef,undef)=@_;
261             # 0 Current object class
262             # 1 fake_id
263             # 2 Data::Result Object ( passed into $self->queue_result )
264             # 3 undef
265             # 4 undef
266             };
267              
268             =cut
269              
270             sub queue_result {
271 4     4 1 4950 my ($self,$cb,$result)=@_;
272 4 100       14 $cb=\&block_cb unless $cb;
273 4 50       11 $result=$self->new_false("unknown error") unless defined($result);
274 4         8 my $id;
275             $id=$self->agent->add_result(sub {
276 4     4   13 $cb->($self,$id,$result,undef,undef);
277 4         132 });
278             }
279              
280             sub has_fake_jobs {
281 0     0 0 0 return $_[0]->agent->has_fake_jobs;
282             }
283              
284             =item * my $results=$self->block_on_ids(@ids);
285              
286             Scalar context returns an array ref.
287              
288             =item * my @results=$self->block_on_ids(@ids);
289              
290             Returns a list of array refrences.
291              
292             Each List refrence contains the follwing
293              
294             0: Data::Result
295             1: HTTP::Request
296             2: HTTP::Result
297              
298             Example
299              
300             my @results=$self->block_on_ids(@ids);
301             foreach my $set (@results) {
302             my ($result,$request,$response)=@{$set};
303             if($result)
304             ...
305             } else {
306             ...
307             }
308             }
309              
310             =cut
311              
312             sub block_on_ids {
313 6     6 1 23 my ($self,@ids)=@_;
314 6         17 my @init=@ids;
315              
316 6         109 $self->agent->block_for_results_by_id(@ids);
317 6         772 my $ref={};
318              
319 6         11 while($#{$self->block_for_more}!=-1) {
  6         105  
320 0         0 @ids=@{$self->block_for_more};
  0         0  
321 0         0 @{$self->block_for_more}=();
  0         0  
322 0         0 $self->agent->run_next;
323 0         0 $self->agent->block_for_results_by_id(@ids);
324             }
325              
326 6         126 my $results=[delete @{$self->result_map}{@init}];
  6         112  
327 6 50       69 return wantarray ? @{$results} : $results;
  0         0  
328             }
329              
330             =item * $self->add_ids_for_blocking(@ids);
331              
332             This method solves the chicken and the egg senerio when a calback generates other callbacks. In a non blocking context this is fine, but in a blocking context there are 2 things to keep in mind: 1. The jobs created by running the inital request didn't exist when the id was created. 2. The outter most callback id must always be used when processing the final callback or things get wierd.
333              
334             The example here is a litteral copy paste from L<Net::AppDynamics::REST>
335              
336             sub que_walk_all {
337             my ($self,$cb)=@_;
338              
339             my $state=1;
340             my $data={};
341             my $total=0;
342             my @ids;
343              
344             my $app_cb=sub {
345             my ($self,$id,$result,$request,$response)=@_;
346              
347             if($result) {
348             foreach my $obj (@{$result->get_data}) {
349             $data->{ids}->{$obj->{id}}=$obj;
350             $obj->{our_type}='applications';
351             $data->{applications}->{$obj->{name}}=[] unless exists $data->{applications}->{$obj->{name}};
352             push @{$data->{applications}->{$obj->{name}}},$obj->{id};
353             foreach my $method (qw(que_list_nodes que_list_tiers que_list_business_transactions)) {
354             ++$total;
355             my $code=sub {
356             my ($self,undef,$result,$request,$response)=@_;
357             return unless $state;
358             return ($cb->($self,$id,$result,$request,$response,$method,$obj),$state=0) unless $result;
359             --$total;
360             foreach my $sub_obj (@{$result->get_data}) {
361             my $target=$method;
362             $target=~ s/^que_list_//;
363              
364             foreach my $field (qw(name machineName)) {
365             next unless exists $sub_obj->{$field};
366             my $name=uc($sub_obj->{$field});
367             $data->{$target}->{$name}=[] unless exists $data->{$target}->{$name};
368             push @{$data->{$target}->{$name}},$sub_obj->{id};
369             }
370             $sub_obj->{ApplicationId}=$obj->{id};
371             $sub_obj->{ApplicationName}=$obj->{name};
372             $sub_obj->{our_type}=$target;
373             $data->{ids}->{$sub_obj->{id}}=$sub_obj;
374             }
375              
376             if($total==0) {
377             return ($cb->($self,$id,$self->new_true($data),$request,$response,'que_walk_all',$obj),$state=0)
378             }
379             };
380             push @ids,$self->$method($code,$obj->{id});
381             }
382             }
383             } else {
384             return $cb->($self,$id,$result,$request,$response,'que_list_applications',undef);
385             }
386             $self->add_ids_for_blocking(@ids);
387             };
388              
389             return $self->que_list_applications($app_cb);
390             }
391              
392             =cut
393              
394             sub add_ids_for_blocking {
395 0     0 1 0 my ($self,@ids)=@_;
396 0 0       0 return unless $self->is_blocking;
397 0         0 push @{$self->block_for_more},@ids;
  0         0  
398             }
399              
400             =item * my $code=$self->block_cb($id,$result,$request,$response);
401              
402             For internal use Default callback method used for all que_ methods.
403              
404             =cut
405              
406             sub block_cb {
407 6     6 1 20 my ($self,$id,$result,$request,$response)=@_;
408 6         110 $self->result_map->{$id}=[$result,$request,$response];
409             }
410              
411             =item * my $cb=$self->get_block_cb
412              
413             For Internal use, Returns the default blocking callback: \&block_cbblock_cb
414              
415             =cut
416              
417             sub get_block_cb {
418 5     5 1 37 return \&block_cb;
419             }
420              
421             =back
422              
423             =head1 Non-Blocking Interfaces
424              
425             Every Non-Blocking method has a contrasting blocking method that does not accept a code refrence. All of the blocking interfaces are auto generated using AUTOLOAD. This section documents the non blocking interfaces.
426              
427             All Non Blocking methods provide the following arguments to the callback.
428              
429             my $code=sub {
430             my ($self,$id,$result,$request,$response)=@_;
431             if($result) {
432             print Dumper($result->get_data);
433             } else {
434             warn $result;
435             }
436             }
437              
438             $self->que_xxx($code,$sql);
439              
440             The code refrence $code will be calld when the HTTP::Response has been recived.
441              
442             Callback variables
443              
444             $self
445             This Net::AppDynamics::REST Object
446             $id
447             The Job ID ( used internally )
448             $result
449             A Data::Result Object, when true it contains the results, when false it contains why things failed
450             $request
451             HTTP::Requst Object that was sent to SolarWinds to make this request
452             $response
453             HTTP::Result Object that represents the response from SolarWinds
454              
455             =head1 Blocking Interfaces
456              
457             All Blocking interfaces are generated with the AUTOLOAD method. Each method that begins with que_xxx can be calld in a blocking method.
458              
459             Example:
460              
461             # my $id=$self->que_list_applications(sub {});
462              
463             # can called as a blocking method will simply return the Data::Result object
464             my $result=$self->list_applications;
465              
466             =cut
467              
468             sub AUTOLOAD {
469 5     5   7842 my ($self,@args)=@_;
470              
471 5         35 my $method=$AUTOLOAD;
472 5         29 $method=~ s/^.*:://s;
473 5 50       34 return if $method eq 'DESTROY';
474              
475 5         129 $self->is_blocking(1);
476 5         241 my $que_method="que_$method";
477 5 50       19 unless($self->can($que_method)) {
478 0         0 croak "Undefined subroutine $method";
479             }
480              
481 5         20 my @ids=$self->$que_method($self->get_block_cb,@args);
482 5         96 $self->agent->run_next;
483 5         1595 my $result=$self->block_on_ids(@ids)->[0]->[0];
484              
485 5         126 $self->is_blocking(0);
486 5         215 return $result;
487             }
488              
489             sub can {
490 22     22 0 52510 my ($self,$method)=@_;
491 22         133 my $sub=$self->SUPER::can($method);
492              
493 22 100       90 return $sub if $sub;
494              
495 4         15 my $que_method="que_$method";
496 4 100       36 return undef unless $self->SUPER::can($que_method);
497              
498             $sub=sub {
499 1     1   317 $AUTOLOAD=$method;
500 1         6 $self->AUTOLOAD(@_);
501 1         8 };
502              
503 1         4 return $sub;
504             }
505              
506       1 0   sub DEMOLISH { }
507              
508             =head1 See Also
509              
510             L<https://docs.appdynamics.com/display/PRO43/AppDynamics+APIs>
511              
512             L<AnyEvent::HTTP::MultiGet>
513              
514             =head1 AUTHOR
515              
516             Michael Shipper L<mailto:AKALINUX@CPAN.ORG>
517              
518             =cut
519              
520             1;