File Coverage

blib/lib/AnyEvent/UA.pm
Criterion Covered Total %
statement 16 30 53.3
branch n/a
condition n/a
subroutine 6 9 66.6
pod n/a
total 22 39 56.4


line stmt bran cond sub pod time code
1             package AnyEvent::UA::Req;
2              
3             sub new {
4 0     0     my $pk = shift;
5 0           my ($method,$uri) = (shift,shift);
6 0           my $self = bless {@_}, $pk;
7 0           $self->{method} = $method;
8 0           $self->{uri} = $uri;
9 0           $self;
10             }
11              
12             sub error {
13 0     0     my $self = shift;
14 0           $self->{cb}( undef, HTTP::Easy::Headers->new->HTTP(@_) );
15 0           %$self = ();
16             }
17              
18             package AnyEvent::UA::Con;
19              
20             sub new {
21 0     0     my $pk = shift;
22 0           my $self = bless {@_}, $pk;
23 0           $self;
24             }
25              
26             package AnyEvent::UA;
27              
28             #use strict;
29             #use warnings;
30 1     1   26196 use common::sense;
  1         10  
  1         7  
31              
32 1     1   879 use AE;
  1         16080  
  1         37  
33 1     1   1199 use AnyEvent::DNS;
  1         41417  
  1         47  
34 1     1   14 use AnyEvent::Socket;
  1         2  
  1         114  
35 1     1   1266 use AnyEvent::Handle;
  1         9242  
  1         41  
36 1     1   427 use HTTP::Easy::Headers;
  0            
  0            
37             use HTTP::Easy::Cookies;
38             use Scalar::Util 'weaken';
39              
40              
41             =head1 NAME
42              
43             AnyEvent::UA - HTTP/1.1 UserAgent using AnyEvent
44              
45             =cut
46              
47             our $VERSION = '0.01_01';#$VERSION = eval($VERSION);
48              
49             =head1 SYNOPSIS
50              
51             Full docs to be done, so just an example.
52            
53             my $ua = AnyEvent::UA->new();
54             $ua->req(GET => 'HTTP://www.google.ru:80', cb => sub {
55             my ($body,$headers) = @_;
56             });
57              
58             =head1 DESCRIPTION
59              
60             This module is alpha quality. It was not tested perfectly. Use it on your own risk. Interfaces and implementation may be changed.
61              
62             =cut
63              
64             sub new {
65             my $pk = shift;
66             my $self = bless {}, $pk;
67             my %args = @_;
68             $self->{headers} = {
69             # 'accept' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
70             # 'user-agent' => 'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.10) Gecko/2009042315 Firefox/3.0.10',
71             'referer' => undef,
72             # 'accept-language' => 'ru,en-us;q=0.8,en;q=0.5,ru-ru;q=0.3',
73             # 'accept-encoding' => 'gzip',
74             'accept-charset' => 'utf-8,windows-1251;q=0.7,*;q=0.7',
75             'connection' => 'keep-alive',
76             %{ $args{headers} || {} },
77             };
78             $self->{cv} = $args{cv} || AE::cv;
79             $self->{cookie} //= HTTP::Easy::Cookies->new();
80             #$self->{auth} = {};
81             #$self->{requests} = [];
82             #$self->{domain} = $args{domain} || '.odnoklassniki.ru';
83             $self->{debug} = $args{debug} // 1;
84             $self->{proxy} = $args{proxy} if exists $args{proxy};
85             $self;
86             }
87              
88              
89             our $TIMEOUT = 10;
90              
91             sub connect : method {
92             my ($self,$host,$port,%args) = @_;
93             # TODO:
94             # * slots (max-open-con)
95             # * single resolve queue
96             $self->{cv}->begin;
97             my %state;
98             my $con = sub {
99             if (my $ra = shift) {
100             warn "ready to con $ra $port";
101             $state{connect} = tcp_connect $ra,$port,sub {
102             my $fh = shift;
103             @_ = ();
104             if( $fh ) {
105             $args{cb}($fh);
106             } else {
107             $args{cb}(undef,"$!");
108             }
109             %state = ();
110             },$args{on_prepare} || sub { $args{timeout} || $TIMEOUT };
111             } else {
112             $args{cb}(undef,@_);
113             $self->{cv}->end;
114             }
115             };
116             my $ip = $self->{dns}{$host};
117             if ($ip) {
118             push @$ip, my $ra = shift @$ip;
119             $con->($ra);
120             } else {
121             AnyEvent::DNS::a $host, sub {
122             if (@_) {
123             $self->{dns}{$host} = [@_];
124             $con->($_[-1]);
125             } else {
126             $con->(undef, "$!");
127             }
128             };
129             }
130             return defined wantarray ? AnyEvent::Util::guard { %state = (); } : undef;
131             }
132              
133             our $qr_nl = qr{\015?\012}o;
134             our $qr_nlnl = qr{(?
135             our $MAX_RECURSE = 10;
136              
137             sub decode_uri {
138             my $self = shift;
139             my $uri = shift;
140             my $port = { http => 80, https => 443, }->{ lc $uri->scheme } or return;
141             my $realport = $uri->port;
142             my $host = lc $uri->host;
143             #warn "$host : $port";
144             my $host_header = $port != $realport ? "$host:$realport" : $host;
145             my $proxy;
146             my ($rhost, $rport, $rscheme, $rpath); # request host, port, path
147             if ($proxy) {
148             ($rpath, $rhost, $rport, $rscheme) = ("$uri", @$proxy);
149             $rscheme = "http" unless defined $rscheme;
150             # don't support https requests over https-proxy transport,
151             # can't be done with tls as spec'ed, unless you double-encrypt.
152             $rscheme = "http" if $uri->scheme eq "https" && $rscheme eq "https";
153             } else {
154             ($rhost, $rport, $rscheme, $rpath) = ($host,$realport,$uri->scheme,$uri->path);
155             }
156             return ($rhost, $rport, $rscheme, $rpath, $host_header);
157             }
158              
159             sub req {
160             my $self = shift;
161             my ($method, $uri, %args) = @_;
162             use URI;
163             $uri = URI->new($uri) unless ref $uri;
164             $uri->path('/') unless length $uri->path;
165             my %state;
166             my $e = sub { my ($code,$mess) = @_; %state = (); $args{cb}(undef, { Status => $code, Reason => $mess, URL => $uri }); };
167            
168             my ($host, $port, $scheme, $path, $host_header) = $self->decode_uri($uri)
169             or return $e->(599);
170            
171             my $headers = HTTP::Easy::Headers->new( { %{$self->{headers}}, host => $host_header } );
172             warn "($host, $port, $scheme, $path) \n";
173             my $conkey = "$host:$port";
174             my $id;
175             if (exists $self->{ka}{$conkey}) {
176             $id = $self->{ka}{$conkey};
177             warn "Have KA for $conkey: $self->{con}{ $id }";
178             push @{ $self->{con}{ $id }{r} }, AnyEvent::UA::Req->new(
179             $method, $uri, %args, headers => $headers,
180             );
181             #$self->rr($h, $method, $uri, %args, path => $path, headers => $headers);
182             $self->rr2($id);
183             return;
184             } else {
185             # TODO:
186             # push req to r, connect, handle all
187             {
188             weaken( my $this = $self );
189             my $con = {
190             host => $host,
191             port => $port,
192             r => [],
193             # TODO
194             # $self->{keep_alive} ? (
195             # ka => AE::timer 300,0,sub {
196             # $self or return;
197             # delete $self->{con}{$id};
198             # },
199             # ) : (),
200             };
201             $id = int $con;
202             $con->{id} = $id;
203             $self->{con}{$id} = $con;
204             $self->{ka}{$conkey} = $id;
205             $con->{close} = sub {
206             $this or return;
207             exists $this->{con}{$id} or return;
208             for(@{ $this->{con}{$id}{r} }) {
209             $_->error(599,$_[0]);
210             }
211             delete $this->{con}{$id};
212             };
213             }
214             push @{ $self->{con}{ $id }{r} }, AnyEvent::UA::Req->new(
215             $method, $uri, %args, headers => $headers,
216             );
217             }
218             my $timeout = $args{timeout} || $TIMEOUT;
219             my $proxy = $args{proxy};# || $PROXY;
220             $state{connect} =
221             $self->connect(
222             $host, $port,
223             timeout => $timeout,
224             on_prepare => $args{on_prepare},
225             cb => sub {
226             if (my $fh = shift) {
227             warn "connected 1";
228             return unless delete $state{connect};
229             warn "connected 2. id = $id";
230             my $h = AnyEvent::Handle->new(
231             fh => $fh,
232             timeout => $timeout,
233             peername => $host,
234             on_eof => sub { warn "EOF"; delete($self->{con}{$id})->{close}("Unexpected end-of-file") },
235             on_error => sub { warn "ERR @_"; delete($self->{con}{$id})->{close}( $_[2]); },
236             #tls_ctx => $arg{tls_ctx},
237             );
238             $self->{con}{$id}{h} = $h;
239              
240             # TODO: limit KA conns
241             # (re-)configure handle
242             my $request = sub {
243             # Connection initially established
244             #$self->rr($h, $method, $uri, %args, path => $path, headers => $headers);
245             $self->rr2($id);
246             };#END $request
247             # now handle proxy-CONNECT method
248             $h->starttls ("connect") if $scheme eq "https";
249             if ($proxy and $scheme eq "https") {
250             my $peer = (my $uhost = $uri->host).':'.$uri->port;
251             $h->push_write ("CONNECT $peer HTTP/1.0\015\012Host: $uhost\015\012\015\012");
252             $h->push_read (line => $qr_nlnl, sub {
253             $_[1] =~ /^HTTP\/([0-9\.]+) \s+ ([0-9]{3}) (?: \s+ ([^\015\012]*) )?/ix
254             or return $e->(599, "Invalid proxy connect response ($_[1])");
255             if ($2 == 200) {
256             $path = $uri->path;
257             $self->{con}{$id}{type} = 'raw';
258             $self->rr2($id);
259             } else {
260             return $e->($2,$3);
261             }
262             });
263             } else {
264             $h->starttls ("connect") if $scheme eq "https" && !exists $state{handle}{tls};
265             $self->rr2($id);
266             }
267             } else {
268             warn "Got error @_";
269             return $e->(599,@_);
270             }
271             }
272             );
273             return;
274             }
275              
276             sub rr2 {
277             my $self = shift;
278             my $id = shift;
279             return warn "no such connection $id" unless exists $self->{con}{$id};
280             my $con = $self->{con}{$id};
281             return warn ("Not connected yet"), unless $con->{h};
282             return warn ("No more requests for $id"), $con->{h}->timeout(undef) unless @{ $con->{r} };
283             #while (@{ $con->{r} }) {
284             my $r = shift @{ $con->{r} };
285             warn "Run request $r->{method} $r->{uri} over $con->{id}";
286             if ($con->{type} eq 'proxy') {
287             return $r->error(599, "Proxy not implemented");
288             } else {
289             my $path = $r->{uri}->path_query;
290             $con->{h}->push_write (
291             "$r->{method} $path HTTP/1.1\015\012" .
292             $r->{headers}->encode . "\015\012" .
293             (delete $r->{body})
294             );
295             $con->{h}->push_read (line => $qr_nl, sub {
296             #return unless exists $self->{con}{$id};
297             $_[1] =~ /^HTTP\/([0-9\.]+) \s+ ([0-9]{3}) (?: \s+ ([^\015\012]*) )?/ixo
298             or return $r->error( 599, "Invalid server response ($_[1])" );
299            
300             my $status = $2;my $reason = $3;my $http_version = $1;
301              
302             # headers, could be optimized a bit
303             $con->{h}->unshift_read (line => $qr_nlnl, sub {
304             my $uri = $r->{uri};
305             my $method = $r->{method};
306             my $hdr = HTTP::Easy::Headers->decode($_[1], base => $uri);
307             $hdr->{Status} = $status;
308             $hdr->{Reason} = $reason;
309             # TODO: check correctness?
310             # or return $r->error(599, "Garbled response headers");
311             $self->{cookie}->decode($hdr->{"set-cookie"}, host => $uri->host) if exists $hdr->{"set-cookie"};
312             $self->{cookie}->decode($hdr->{"set-cookie2"}, host => $uri->host) if exists $hdr->{"set-cookie2"};
313             $self->{cookie}->decode($hdr->{"set-cookie3"}, host => $uri->host) if exists $hdr->{"set-cookie3"};
314              
315             my $redirect;
316             my $recurse = 0;# TODO: exists $args{recurse} ? delete $args{recurse} : $MAX_RECURSE;
317              
318             if ($recurse) {
319             if ($status =~ /^30[12]$/ and $method ne "POST") {
320             # apparently, mozilla et al. just change POST to GET here
321             # more research is needed before we do the same
322             $redirect = 1;
323             }
324             elsif ($status == 303) {
325             # even http/1.1 is unclear on how to mutate the method
326             $method = "GET" unless $method eq "HEAD";
327             $redirect = 1;
328             }
329             elsif ($status == 307 and $method =~ /^(?:GET|HEAD)$/) {
330             $redirect = 1;
331             }
332             }
333              
334             my $finish = sub {
335             #$con->destroy if $con;
336             #%state = ();
337              
338             if ($redirect && exists $hdr->{location}) {
339             # we ignore any errors, as it is very common to receive
340             # Content-Length != 0 but no actual body
341             # we also access %hdr, as $_[1] might be an erro
342             #http_request ($method => $hdr{location}, %arg, recurse => $recurse - 1, $cb);
343             warn "Redirect => $hdr->{location}";
344             } else {
345             if (exists $_[1]{'content-encoding'}) {
346             if (lc($_[1]{'content-encoding'}) =~ /^(?:x-)?gzip$/) {
347             eval{
348             my $def = Compress::Zlib::memGunzip($_[0]);
349             if (defined $def) {
350             $_[0] = $def;
351             #warn "Page deflated from $hdr->{'content-encoding'}" if $self->{debug};
352             1;
353             } else { 0 }
354             } or do {
355             warn "Deflate failed: $@";
356             }
357             } else {
358             warn "Unsupported content-encoding method: $_[1]{'content-encoding'}";
359             }
360             }
361             $r->{cb}($_[0], $_[1]);
362             }
363             $self->rr2($id);
364             };
365              
366             my $len = $hdr->{"content-length"};
367              
368             # if (!$redirect && $args{on_header} && !$args{on_header}($hdr)) {
369             # $finish->(undef, { Status => 598, Reason => "Request cancelled by on_header", URL => $uri });
370             # }
371             # elsif (
372             if(
373             $status =~ /^(?:1..|[23]04)$/
374             or $method eq "HEAD"
375             or (defined $len && !$len)
376             ) {
377             # no body
378             $finish->("", $hdr);
379             }
380             else {
381             #warn dumper $hdr,$self->{cookie};
382             if (lc $hdr->{'transfer-encoding'} eq 'chunked') {
383             my $body = '';
384             my $get_chunk;$get_chunk = sub {
385             $con->{h}->unshift_read( regex => qr{([a-f0-9]{1,32})(?:[\011\040]+[^\012]{0,255})?\015?\012}o,sub {
386             my $chunk = hex($1);
387             if ($chunk > 0) {
388             $get_chunk->();
389             #warn "need chunk $chunk";
390             $_[0]->unshift_read(chunk => $chunk, sub {
391             $body .= $_[1];
392             });
393             } else {
394             undef $get_chunk;
395             #warn "Got all chunks, read trailer";
396             $_[0]->unshift_read(line => $qr_nlnl, sub {
397             #warn "Got trailer $_[1]";
398             $finish->($body,$hdr);
399             });
400             }
401             });
402             };
403             $get_chunk->();
404             } else {
405             $_[0]->on_eof (undef);
406             if ($len) {
407             warn "ready for body (+$len)";
408             $_[0]->on_error (sub { $finish->(undef, $hdr->HTTP(599,$_[2])) });
409             $_[0]->unshift_read(chunk => $len, sub {
410             $finish->($_[1],$hdr);
411             });
412             } else {
413             warn "ready for body until eof";
414             $_[0]->on_error (sub {
415             $! == Errno::EPIPE || !$!
416             ? $finish->(delete $_[0]{rbuf}, $hdr)
417             : $finish->(undef, $hdr->HTTP(599,$_[2]));
418             });
419             $_[0]->on_read (sub { });
420             }
421             }
422            
423             }
424             });
425             });
426            
427             }
428             #}
429             }
430              
431             sub rr { # request/response
432             my $self = shift;
433             my $con = shift;
434             my ($method, $uri, %args) = @_;@_ = ();
435             my $e = sub { my ($code,$mess) = @_; undef $con; $args{cb}(undef, { Status => $code, Reason => $mess, URL => $uri }); };
436             my $recurse = exists $args{recurse} ? delete $args{recurse} : $MAX_RECURSE;
437             warn "Run request $method $uri";
438            
439             # send request
440             $con->push_write (
441             "$method $args{path} HTTP/1.1\015\012"
442             . $args{headers}->encode
443             . "\015\012"
444             . (delete $args{body})
445             );
446             if ($args{body_cb}) {
447             my $written = 0;
448             my $need = $args{headers}{"content-length"};
449             $con->on_drain(sub {
450             $args{body_cb}(sub {
451             shift if @_ and length $_[0] == 0;
452             use bytes;
453             if (@_) {
454             my $chunk = shift;
455             my $left = $need - $written;
456             $written += ( my $length = length $chunk );
457             #warn "Written chunk=$length. now have written=$written and left=".($need - $written);
458             if ($written >= $need) {
459             if ($written > $need) {
460             $chunk = substr($chunk,0,$left);
461             warn "got more data $written, than content-length $need, truncated at @{[ (caller)[1,2] ]}\n";
462             }
463             $con->on_drain(undef);
464             undef $args{body_cb};
465             }
466             $con->push_write($chunk);
467             } else {
468             $con->on_drain(undef);
469             undef $args{body_cb};
470             if ($written < $need) {
471             return $e->(599, "Insufficient ".($need-$written)." bytes data from body_cb. need $need, got $written");
472             }
473             }
474             });
475             });
476             # TODO
477             #%state or return;
478             }
479              
480             delete $args{headers};
481              
482             # status line
483             $con->push_read (line => $qr_nl, sub {
484             $_[1] =~ /^HTTP\/([0-9\.]+) \s+ ([0-9]{3}) (?: \s+ ([^\015\012]*) )?/ixo
485             or return $e->(599, "Invalid server response ($_[1])");
486            
487             my $status = $2;my $reason = $3;my $http_version = $1;
488              
489             # headers, could be optimized a bit
490             $con->unshift_read (line => $qr_nlnl, sub {
491             my $hdr = HTTP::Easy::Headers->decode($_[1], base => $uri);
492             $self->{cookie}->decode($hdr->{"set-cookie"}, host => $uri->host) if exists $hdr->{"set-cookie"};
493             $self->{cookie}->decode($hdr->{"set-cookie2"}, host => $uri->host) if exists $hdr->{"set-cookie2"};
494             $self->{cookie}->decode($hdr->{"set-cookie3"}, host => $uri->host) if exists $hdr->{"set-cookie3"};
495             # TODO: check correctness?
496             # or return (%state = (), $cb->(undef, { Status => 599, Reason => "Garbled response headers", URL => $url }));
497              
498             my $redirect;
499              
500             if ($recurse) {
501             if ($status =~ /^30[12]$/ and $method ne "POST") {
502             # apparently, mozilla et al. just change POST to GET here
503             # more research is needed before we do the same
504             $redirect = 1;
505             }
506             elsif ($status == 303) {
507             # even http/1.1 is unclear on how to mutate the method
508             $method = "GET" unless $method eq "HEAD";
509             $redirect = 1;
510             }
511             elsif ($status == 307 and $method =~ /^(?:GET|HEAD)$/) {
512             $redirect = 1;
513             }
514             }
515              
516             my $finish = sub {
517             #$con->destroy if $con;
518             #%state = ();
519              
520             # set-cookie processing
521             $self->{cookie}->decode($_[1]{"set-cookie"}, host => $uri->host);
522             #$DEBUG_RECV->($_[1]{URL},$_[0],$_[1]) if defined $DEBUG_RECV;
523              
524             if ($redirect && exists $hdr->{location}) {
525             # we ignore any errors, as it is very common to receive
526             # Content-Length != 0 but no actual body
527             # we also access %hdr, as $_[1] might be an erro
528             #http_request ($method => $hdr{location}, %arg, recurse => $recurse - 1, $cb);
529             warn "Redirect => $hdr->{location}";
530             } else {
531             warn "OK";
532             $args{cb}($_[0], $_[1]);
533             }
534             };
535              
536             my $len = $hdr->{"content-length"};
537              
538             if (!$redirect && $args{on_header} && !$args{on_header}($hdr)) {
539             $finish->(undef, { Status => 598, Reason => "Request cancelled by on_header", URL => $uri });
540             }
541             elsif (
542             $status =~ /^(?:1..|[23]04)$/
543             or $method eq "HEAD"
544             or (defined $len && !$len)
545             ) {
546             # no body
547             $finish->("", $hdr);
548             }
549             else {
550             #warn dumper $hdr,$self->{cookie};
551             if (lc $hdr->{'transfer-encoding'} eq 'chunked') {
552             my $body = '';
553             my $get_chunk;$get_chunk = sub {
554             $con->unshift_read( regex => qr{([a-f0-9]{1,255})\015?\012},sub {
555             my $chunk = hex($1);@_ = ();
556             if ($chunk > 0) {
557             #warn "need chunk $chunk";
558             $get_chunk->();
559             $con->unshift_read(chunk => $chunk, sub {
560             $body .= $_[1];
561             });
562             } else {
563             undef $get_chunk;
564             warn "Got all chunks";
565             $finish->($body,$hdr);
566             }
567             });
568             };
569             $get_chunk->();
570             } else {
571             $_[0]->on_eof (undef);
572             if ($len) {
573             warn "ready for body (+$len)";
574             $_[0]->on_error (sub { $finish->(undef, { Status => 599, Reason => $_[2], URL => $uri }) });
575             $_[0]->unshift_read(chunk => $len, sub {
576             $finish->($_[1],$hdr);
577             });
578             } else {
579             warn "ready for body until eof";
580             $_[0]->on_error (sub {
581             $! == Errno::EPIPE || !$!
582             ? $finish->(delete $_[0]{rbuf}, $hdr)
583             : $finish->(undef, { Status => 599, Reason => $_[2], URL => $uri });
584             });
585             $_[0]->on_read (sub { });
586             }
587             }
588            
589             }
590             });
591             });
592              
593             }
594              
595             sub http_request;
596             sub req1 {
597             my $self = shift;
598             my %args = @_;
599             $self->{cv}->begin;
600             http_request +
601             ( $args{form} ? 'POST' : 'GET') => "$args{uri}",
602             $args{form} ? (
603             body => _postdata(@{ $args{form} }),
604             ) : (),
605             headers => {
606             %{ $self->{headers} },
607             $args{form} ? ( 'content-type' => 'application/x-www-form-urlencoded' ) : (),
608             %{ $args{headers} || {} }
609             },
610             cookie_jar => $self->{cookie},
611             timeout => 10,
612             $self->next_proxy(),
613             cb => sub {
614             push @{$self->{requests}}, join(' ',$_[1]{Status}, ':', ($args{form} ? 'POST' : 'GET'), $args{uri} );
615             #$self->{requests}++;
616             if( my $cookies = $_[1]{'set-cookie'} ) {
617             local $self->{uri} = URI->new($_[1]{URL});
618             $self->_parse_cookies($cookies);
619             }
620             if (exists $args{raw}) {
621             $args{raw}(@_);
622             } else {
623             my ($body,$hdr) = @_;
624             if ($hdr->{Status} =~ /^(200|302)$/) {
625             $self->{uri} = URI->new($hdr->{URL});
626             $self->{page} = $self->getpage;
627             if (exists $hdr->{'content-encoding'}) {
628             if (lc($hdr->{'content-encoding'}) eq 'gzip') {
629             eval{
630             my $def = Compress::Zlib::memGunzip($body);
631             if (defined $def) {
632             $body = $def;
633             #warn "Page deflated from $hdr->{'content-encoding'}" if $self->{debug};
634             1;
635             } else { 0 }
636             } or do {
637             warn "Deflate failed: $@";
638             }
639             } else {
640             warn "Unsupported content-encoding method: $hdr->{'content-encoding'}";
641             }
642             }
643             warn "Req $self->{uri} / $self->{page}\n"._postdata(@{ $args{form} })."\n ok" if $self->{debug};
644             $args{cb}( { body => $body, head => $hdr } );
645             } else {
646             $args{cb}(undef, "req($hdr->{URL}) failed: $hdr->{Status}: $hdr->{Reason}");
647             }
648             }
649             $self->{cv}->end;
650             },
651             ;
652             return;
653             }
654              
655             =head1 AUTHOR
656              
657             Mons Anderson, C<< >>
658              
659             =head1 ACKNOWLEDGEMENTS
660              
661             Many parts of this module was derived from L
662              
663             =head1 LICENSE
664              
665             This program is free software; you can redistribute it and/or modify it
666             under the terms of either: the GNU General Public License as published
667             by the Free Software Foundation; or the Artistic License.
668              
669             =cut
670              
671             1; # End of AnyEvent::UA