File Coverage

blib/lib/AnyEvent/ReverseHTTP.pm
Criterion Covered Total %
statement 36 85 42.3
branch 0 20 0.0
condition 0 9 0.0
subroutine 12 17 70.5
pod 0 2 0.0
total 48 133 36.0


line stmt bran cond sub pod time code
1             package AnyEvent::ReverseHTTP;
2              
3 1     1   7 use strict;
  1         2  
  1         38  
4 1     1   21 use 5.008_001;
  1         4  
  1         523  
5             our $VERSION = '0.05';
6              
7 1     1   8 use Carp;
  1         2  
  1         107  
8 1     1   1111 use AnyEvent::Util;
  1         24751  
  1         121  
9 1     1   1268 use AnyEvent::HTTP;
  1         25426  
  1         102  
10 1     1   965 use HTTP::Request;
  1         45656  
  1         48  
11 1     1   2319 use HTTP::Response;
  1         11946  
  1         43  
12 1     1   14 use URI::Escape;
  1         2  
  1         100  
13 1     1   7 use Scalar::Util;
  1         2  
  1         60  
14              
15 1     1   6 use base qw(Exporter);
  1         3  
  1         132  
16             our @EXPORT = qw(reverse_http);
17              
18 1     1   1428 use Any::Moose;
  1         67457  
  1         9  
19              
20             has endpoint => (
21             is => 'rw', isa => 'Str',
22             required => 1, default => "http://www.reversehttp.net/reversehttp",
23             );
24              
25             has label => (
26             is => 'rw', isa => 'Str',
27             required => 1,
28             lazy => 1, default => sub {
29             require Digest::SHA;
30             require Time::HiRes;
31             return Digest::SHA::sha1_hex($$ . Time::HiRes::gettimeofday() . {});
32             },
33             );
34              
35             has token => (
36             is => 'rw', isa => 'Str',
37             default => '-',
38             );
39              
40             has on_register => (
41             is => 'rw', isa => 'CodeRef',
42             default => sub { sub { warn "Public Application URL: $_[0]\n" } },
43             );
44              
45             has on_error => (
46             is => 'rw', isa => 'CodeRef',
47             default => sub { sub { Carp::croak(@_) } },
48             );
49              
50             has on_request => (
51             is => 'rw', isa => 'CodeRef',
52             default => sub { sub { Carp::croak("on_request handler is not defined!") } },
53             );
54              
55             sub reverse_http {
56 0     0 0   my $cb = pop;
57              
58 0 0         my @args =
    0          
    0          
59             @_ == 1 ? qw(label) :
60             @_ == 2 ? qw(label token) :
61             @_ >= 3 ? qw(endpoint label token) : ();
62              
63 0           my %args; @args{@args} = @_;
  0            
64 0           return __PACKAGE__->new(%args, on_request => $cb)->connect;
65             }
66              
67             sub connect {
68 0     0 0   my $self = shift;
69              
70 0           my %query = (name => $self->label);
71 0 0         $query{token} = $self->token if $self->token;
72              
73 0           my $body = join "&", map "$_=" . URI::Escape::uri_escape($query{$_}), keys %query;
74              
75             http_post $self->endpoint, $body, sub {
76 0     0     my($body, $hdr) = @_;
77              
78 0 0 0       if ($hdr->{Status} eq '201' || $hdr->{Status} eq '204') {
79 0           my $app_url = _extract_link($hdr, 'related');
80 0           $self->on_register->($app_url);
81             } else {
82 0           return $self->on_error->("$hdr->{Status}: $hdr->{Reason}");
83             }
84              
85 0           my $poller; $poller = sub {
86 0           my($body, $hdr) = @_;
87              
88 0 0         if ($hdr->{Status} eq '200') {
89 0           my $req = HTTP::Request->parse($body);
90 0           $req->header('Requesting-Client', $hdr->{'requesting-client'});
91 0           my $res = $self->on_request->($req);
92              
93             my $postback = sub {
94 0           my $res = shift;
95              
96             # Duck typing for as_string, but accepts plaintext too for 200
97 0 0 0       unless (Scalar::Util::blessed($res) && $res->can('as_string')) {
98 0           my $content = $res;
99 0           $res = HTTP::Response->new(200);
100 0           $res->content_type('text/plain');
101 0           $res->content($content);
102             }
103              
104 0           $res->protocol("HTTP/1.1"); # Upgrade since reversehttp.net requires so
105              
106             # HTTP::Response->as_string by default adds a new line which could be harmful
107 0           my $res_body = $res->as_string;
108 0 0         chomp $res_body if $res->content_type eq 'text/plain';
109              
110             http_post $hdr->{URL}, $res_body,
111             headers => { 'content-type' => 'message/http' },
112             sub {
113 0           my($body, $hdr) = @_;
114 0 0         if ($hdr->{Status} ne '202') {
115 0           $self->on_error->("$hdr->{Status}: $hdr->{Reason}");
116             }
117 0           };
118 0           };
119              
120             # Return condvar to pass back to event loop
121 0 0 0       if (Scalar::Util::blessed($res) && $res->isa('AnyEvent::CondVar')) {
122 0           $res->cb(sub { $postback->($res->recv) });
  0            
123             } else {
124 0           $postback->($res);
125             }
126             }
127              
128 0           my $next = _extract_link($hdr, 'next');
129 0           http_get $next, $poller;
130 0           };
131              
132 0           my $url = _extract_link($hdr, 'first');
133 0           http_get $url, $poller;
134 0           };
135              
136 0     0     return AnyEvent::Util::guard { undef $self };
  0            
137             }
138              
139             sub _extract_link {
140 0     0     my($hdr, $rel) = @_;
141 0           my @links = $hdr->{link} =~ /<([^>]*)>;\s*rel="\Q$rel\E"/g;
142 0           return $links[0];
143             }
144              
145 1     1   11386 no Any::Moose;
  1         5  
  1         10  
146             __PACKAGE__->meta->make_immutable;
147              
148             1;
149             __END__