File Coverage

lib/Coro/Twiggy.pm
Criterion Covered Total %
statement 82 84 97.6
branch 15 24 62.5
condition 7 14 50.0
subroutine 13 13 100.0
pod 2 2 100.0
total 119 137 86.8


line stmt bran cond sub pod time code
1             package Coro::Twiggy;
2 2     2   112799 use 5.008008;
  2         9  
  2         84  
3 2     2   11 use strict;
  2         4  
  2         59  
4 2     2   10 use warnings;
  2         8  
  2         53  
5              
6 2     2   1662 use Twiggy::Server;
  2         257212  
  2         81  
7 2     2   18 use Scalar::Util 'weaken';
  2         4  
  2         130  
8 2     2   1952 use Coro;
  2         17259  
  2         209  
9 2     2   2290 use Data::Dumper;
  2         13772  
  2         302  
10              
11             our $VERSION = '0.03';
12              
13             =head1 NAME
14              
15             Coro::Twiggy - Coro interface for L
16              
17             =head1 SYNOPSIS
18              
19             use Coro::Twiggy;
20             use Plack::Request;
21             use Coro::AnyEvent;
22              
23             my $application = sub {
24             my ($env) = @_;
25             my $req = Plack::Request->new($env);
26              
27             Coro::AnyEvent::sleep 10;
28             ...
29             return [
30             200,
31             [ 'Content-Type' => 'text/html' ],
32             [ 'Twiggy response after 10 seconds' ]
33             ]
34             };
35              
36              
37             my $server = Coro::Twiggy->new(host => '127.0.0.1', port => 8080);
38             $server->register_service( $application );
39              
40              
41             =head1 DESCRIPTION
42              
43             The server starts Your application in L coroutine and uses its
44             return value to respond to client.
45              
46             Application have to return an B with the following items:
47              
48             =over
49              
50             =item *
51              
52             HTTP-code;
53              
54             =item *
55              
56             an B that contains headers for response;
57              
58             =item *
59              
60             an B that contains body of response.
61              
62             =back
63              
64             To stop server destroy server object
65              
66             =head1 METHODS
67              
68             =cut
69              
70             use constant DEFAULT_SERVICE => sub {
71             [
72 1         7 503,
73             [ 'Content-Type' => 'text/plain' ],
74             [ 'There is no registered PSGI service' ]
75             ]
76 2     2   25 };
  2         4  
  2         1767  
77              
78              
79             =head2 new
80              
81             Constructor. Returns server.
82              
83             =head3 Named arguments
84              
85             =over
86              
87             =item host
88              
89             =item port
90              
91             =item service
92              
93             PSGI application
94              
95             =back
96              
97             =cut
98              
99             sub new {
100 1     1 1 1030 my ($class, %opts) = @_;
101 1         2 my $host = $opts{host};
102 1   50     6 my $port = $opts{port} || 8080;
103 1         3 my $listen = $opts{listen};
104 1   50     10 my $app = $opts{service} || DEFAULT_SERVICE;
105              
106 1         2 my @args;
107 1 50       9 if ($listen) {
    50          
108 0         0 push @args => listen => $listen;
109             } elsif ($port !~ /^\d+$/) {
110 1         4 push @args => listen => [ $port ];
111             } else {
112 0         0 push @args =>
113             host => $host,
114             port => $port;
115             }
116              
117 1         11 my $ts = Twiggy::Server->new(@args);
118              
119 1   33     22 my $self = bless { ts => $ts, app => $app } => ref($class) || $class;
120              
121 1         3 my $this = $self;
122 1         6 $ts->register_service( $this->_app );
123              
124 1         8968 return $self;
125             }
126              
127             sub DESTROY {
128 1     1   2583 my ($self) = @_;
129 1         12 delete $self->{ts}{listen_guards}; # hack: Twiggy has no interface to stop
130 1         199 delete $self->{ts};
131             }
132              
133              
134             =head2 register_service
135              
136             (Re)register PSGI application.
137             Until the event server will respond B<503 Service Unavailable>.
138              
139             =cut
140              
141             sub register_service {
142 5     5 1 11435 my ($self, $cb) = @_;
143 5   50     31 $self->{app} = $cb || DEFAULT_SERVICE;
144             }
145              
146             sub _app {
147 1     1   2 my ($self) = @_;
148 1         6 weaken $self;
149             sub {
150 6     6   105431 my ($env) = @_;
151             sub {
152 6         96 my ($cb) = @_;
153             async {
154 6 50       251 return DEFAULT_SERVICE->() unless $self;
155 6         12 my @res = eval { $self->{app}->($env, $self) };
  6         32  
156 6         500748 my $res = shift @res;
157              
158 6 100       22 if (my $err = $@) {
159 1 50       9 utf8::encode($err) if utf8::is_utf8 $err;
160 1         6 $cb->([ 500, [ 'Content-Type' => 'text/plain' ], [ $err ]]);
161 1         278 return;
162             }
163              
164 5         10 my $msg;
165 5 100       20 unless('ARRAY' eq ref $res) {
166 1         2 $msg = 'PSGI application have to return an ARRAYREF';
167 1         6 goto WRONG_RES;
168             }
169              
170 4 100       19 goto WRONG_RES unless @$res >= 2;
171 3 50       11 push @$res => [] unless @$res > 2;
172              
173             goto WRONG_RES
174 3 50 33     46 unless defined($res->[0]) && $res->[0] =~ /^\d+$/;
175 3 50       12 goto WRONG_RES unless 'ARRAY' eq ref $res->[1];
176 3 50       11 goto WRONG_RES unless 'ARRAY' eq ref $res->[2];
177              
178 3         92 $cb->( $res );
179 3         1095 return;
180              
181              
182 2   100     9 WRONG_RES:
183             $msg ||= "PSGI returned wrong response";
184 2         6 $msg .= "\n\n";
185             {
186 2         3 local $Data::Dumper::Indent = 1;
  2         5  
187 2         4 local $Data::Dumper::Terse = 1;
188 2         5 local $Data::Dumper::Useqq = 1;
189 2         4 local $Data::Dumper::Deepcopy = 1;
190 2         3 local $Data::Dumper::Maxdepth = 0;
191              
192 2         22 my $dump = Data::Dumper->Dump([ $res, @res ]);
193 2 50       299 utf8::downgrade($dump) if utf8::is_utf8 $dump;
194 2         8 $msg .= $dump;
195             }
196              
197 2         12 $cb->( [ 500, [ 'Content-Type', 'text/plain' ], [ $msg ]]);
198 2         843 return;
199             }
200 6         77 }
201 6         48 }
202 1         10 }
203              
204              
205             1;
206              
207             =head1 VCS
208              
209             L
210              
211             =head1 AUTHOR
212              
213             Dmitry E. Oboukhov,
214              
215             =head1 COPYRIGHT AND LICENSE
216              
217             Copyright (C) 2012 by Dmitry E. Oboukhov
218              
219             This library is free software; you can redistribute it and/or modify
220             it under the same terms as Perl itself, either Perl version 5.8.8 or,
221             at your option, any later version of Perl 5 you may have available.
222              
223             =cut