File Coverage

blib/lib/Plack/App/PubSubHubbub/Subscriber.pm
Criterion Covered Total %
statement 38 61 62.3
branch 3 22 13.6
condition 0 6 0.0
subroutine 12 15 80.0
pod 2 7 28.5
total 55 111 49.5


line stmt bran cond sub pod time code
1             package Plack::App::PubSubHubbub::Subscriber;
2 2     2   85567 use strict;
  2         5  
  2         74  
3 2     2   12 use warnings;
  2         4  
  2         66  
4 2     2   1967 use parent qw/Plack::Component/;
  2         475  
  2         15  
5              
6 2     2   20089 use URI;
  2         6246  
  2         61  
7 2     2   3125 use Plack::Request;
  2         105561  
  2         75  
8 2     2   2061 use Plack::Response;
  2         4233  
  2         72  
9 2     2   18 use Plack::Util::Accessor qw( config on_ping on_verify );
  2         4  
  2         10  
10              
11 2     2   1533 use Plack::App::PubSubHubbub::Subscriber::Config;
  2         5  
  2         1343  
12              
13             our $VERSION = 0.2;
14              
15             =head1 NAME
16              
17             Plack::App::PubSubHubbub::Subscriber - PubSubHubbub subscriber implementation as a Plack App
18              
19             =head1 SYNOPSIS
20              
21             use Plack::Builder;
22             use Plack::App::PubSubHubbub::Subscriber;
23             use Plack::App::PubSubHubbub::Subscriber::Config;
24             use Plack::App::PubSubHubbub::Subscriber::Client;
25              
26             my $conf = Plack::App::PubSubHubbub::Subscriber::Config->new(
27             callback => "http://example.tld:8081/callback",
28             lease_seconds => 86400,
29             verify => 'sync',
30             );
31              
32             my $app = Plack::App::PubSubHubbub::Subscriber->new(
33             config => $conf
34             on_verify => sub {
35             my ($topic, $token, $mode, $lease) = @_;
36             ...
37             return 1;
38             },
39             on_ping => sub {
40             my ($content_type, $content, $token) = @_;
41             print $content;
42             },
43             );
44              
45             my $client = Plack::App::PubSubHubbub::Subscriber::Client(
46             config => $conf,
47             );
48              
49             builder {
50             mount $app->callback_path, $app;
51             mount '/subscribe' => sub {
52             ...
53             $client->subscribe( $hub, $topic, $token );
54             ...
55             };
56             mount '/unsubscribe' => sub {
57             ...
58             $client->unsubscribe( $hub, $topic, $token );
59             ...
60             };
61             };
62              
63             =head1 DESCRIPTION
64              
65             PubSubHubbub subscriber implementation in the form of a Plack app
66             and a client. Originally developed for L
67              
68             =head2 $self->config( $conf )
69              
70             Get/Set the L object.
71             This same config object can be use to instanciate the client L
72              
73             =head2 $self->on_ping( sub { my ($content_type, $content, $token) = @_ } )
74              
75             Triggered when a new ping is received, the parameters are the content type, the raw content, and the token in that order.
76             Note that the token is available only if the configuration flag C is set (the default).
77             Also note that, in any case, the token is undef if you didn't use a token to (un)subcribe.
78             The return value is ignore.
79              
80             =head2 $self->on_verify( sub { my ($topic, $token, $mode, $lease) = @_ } )
81              
82             Triggered when a subscribe/unsubscribe request is received, the parameters are the topic, the token, the mode, and the number of seconds of the lease, in that order.
83             Note that the token is undef if you didn't use a token to (un)subcribe.
84             Given these parameters, this coderef must return 1 for verified, or 0 for rejected.
85              
86             =head2 $self->callback_path
87              
88             Return the path part of the callback URL. Useful for doing "mount $app->callback_path, $app;"
89              
90             =cut
91              
92             sub callback_path {
93 2     2 1 1908 my $self = shift;
94 2         14 return URI->new($self->config->callback)->path;
95             }
96              
97             sub call {
98 1     1 1 19544 my($self, $env) = @_;
99 1         11 my $req = Plack::Request->new($env);
100 1 50       18 my $token = $self->config->token_in_path ?
101             extract_token($req) : undef;
102              
103 1 50       5 if ($req->method eq 'POST') {
    0          
104 1 50       13 if (my $ping_cb = $self->on_ping) {
105 1         13 $ping_cb->($req->content_type, $req->content, $token);
106             }
107 1         3438 return success();
108             }
109             elsif ($req->method eq 'GET') {
110 0         0 my $p = $req->parameters;
111 0 0       0 my $mode = $p->{'hub.mode'}
112             or return error_bad_request('hub.mode is missing');
113              
114 0 0 0     0 if ($mode eq 'subscribe' || $mode eq 'unsubscribe') {
115              
116 0 0       0 my $topic = $p->{'hub.topic'}
117             or return error_bad_request('hub.topic is missing');
118 0 0       0 my $challenge = $p->{'hub.challenge'}
119             or return error_bad_request('hub.challenge is missing');
120 0 0       0 my $lease = $p->{'hub.lease_seconds'}
121             or return error_bad_request('hub.lease_seconds is missing');
122              
123 0   0     0 $token //= $p->{'hub.verify_token'};
124              
125 0 0       0 if ($self->on_verify->($topic, $token, $mode, $lease)) {
126 0         0 return success_challenge($challenge);
127             }
128             else {
129 0         0 return error_not_found();
130             }
131             }
132             else {
133 0         0 return error_bad_request('mode unknown');
134             }
135             }
136 0         0 return error_bad_request('unsupported method');
137             }
138              
139             sub extract_token {
140 1     1 0 3 my ($req) = @_;
141 1         6 my ($token) = $req->path =~ /^\/(.+)$/;
142 1         17 return $token;
143             }
144              
145             sub success {
146 1     1 0 11 my $res = Plack::Response->new(200);
147 1         29 return $res->finalize;
148             }
149              
150             sub success_challenge {
151 0     0 0   my ($challenge) = @_;
152 0           my $res = Plack::Response->new(200);
153 0           $res->body($challenge);
154 0           return $res->finalize;
155             }
156              
157             sub error_not_found {
158 0     0 0   my ($challenge) = @_;
159 0           return Plack::Response->new(404)->finalize;
160             }
161              
162             sub error_bad_request {
163 0     0 0   my ($msg) = @_;
164 0           my $res = Plack::Response->new(400);
165 0 0         $res->body($msg) if $msg;
166             # TODO log
167 0           print STDERR "ERROR: $msg\n";
168 0           return $res->finalize;
169             }
170              
171             =head1 LIMITATION
172              
173             the "Authenticated Content Distribution" is not supported.
174              
175             =head1 SEE ALSO
176              
177             L, L
178              
179             =head1 AUTHOR
180              
181             Antoine Imbert, C<< >>
182              
183             =head1 LICENSE AND COPYRIGHT
184              
185             This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
186              
187             =cut
188              
189             1;