File Coverage

blib/lib/Plack/Handler/FCGI/EV.pm
Criterion Covered Total %
statement 22 24 91.6
branch n/a
condition n/a
subroutine 8 8 100.0
pod n/a
total 30 32 93.7


line stmt bran cond sub pod time code
1             package Plack::Handler::FCGI::EV;
2              
3 2     2   59212 use strict;
  2         8  
  2         81  
4 2     2   10 use warnings;
  2         4  
  2         57  
5 2     2   52 use 5.008_001;
  2         11  
  2         156  
6             our $VERSION = '0.01';
7              
8 2     2   2329 use Socket;
  2         10879  
  2         1491  
9 2     2   25 use Fcntl;
  2         4  
  2         620  
10 2     2   4086 use Plack::Util;
  2         35494  
  2         66  
11 2     2   2045 use EV;
  2         6527  
  2         70  
12 2     2   2121 use FCGI::EV;
  0            
  0            
13             use AnyEvent;
14              
15             sub new {
16             my ( $class, %args ) = @_;
17             bless { %args }, $class;
18             }
19              
20             sub register_service {
21             my($self, $app) = @_;
22              
23             my $listen = $self->{listen}[0]
24             or die "FCGI daemon host/port or socket is not specified\n";
25              
26             my $sock;
27             if ($listen =~ /:\d+$/) {
28             my($host, $port) = split /:/, $listen, 2;
29             require IO::Socket::INET;
30             $sock = IO::Socket::INET->new(
31             LocalAddr => $host || '0.0.0.0',
32             LocalPort => $port,
33             ReuseAddr => 1,
34             Proto => 'tcp',
35             Listen => 10,
36             Blocking => 0,
37             );
38             } else {
39             require IO::Socket::UNIX;
40             socket $sock, AF_UNIX, SOCK_STREAM, 0;
41             unlink $listen;
42             my $umask = umask 0;
43             bind $sock, sockaddr_un($listen);
44             umask $umask;
45             listen $sock, SOMAXCONN;
46             fcntl $sock, F_SETFL, O_NONBLOCK;
47             bless $sock, 'IO::Socket::UNIX';
48             }
49              
50             $sock or die "Couldn't launch FCGI daemon on $listen";
51             warn "Running FCGI daemon on $listen\n";
52              
53             # HACK: we return an object and pass it to handler_class. Because it works, and is cleaner
54             my $handler = Plack::Handler::FCGI::EV::Handler->factory($app);
55              
56             my $w = EV::io $sock, EV::READ, sub {
57             my $client = $sock->accept or die "No socket";
58             $client->blocking(0);
59             FCGI::EV->new( $client, $handler );
60             };
61              
62             $self->{_sock} = $sock;
63             $self->{_guard} = $w;
64              
65             return $self;
66             }
67              
68             sub run {
69             my $self = shift;
70             $self->register_service(@_);
71              
72             # Could use EV::run, but this is prone to a crash due to:
73             # syswrite() on closed filehandle GEN1653 at .../lib/site_perl/5.12.3/IO/Stream/EV.pm line 160.
74             # At least AnyEvent will catch these errors
75              
76             # EV::run;
77              
78             AE::cv->recv;
79             }
80              
81             package Plack::Handler::FCGI::EV::Handler;
82              
83             use strict;
84             use Plack::Util;
85              
86             use Carp ();
87             use URI::Escape;
88              
89             sub factory {
90             my($class, $app) = @_;
91             bless { app => $app }, $class;
92             }
93              
94             # HACK: This is called from FCGI::EV - but it's actually an instance
95             # method since we pass our own object to 'handler_class'.
96             sub new {
97             my $factory = shift;
98             my ($server, $env) = @_;
99              
100             my $app = $factory->{app};
101              
102             $env = {
103             SCRIPT_NAME => '',
104             'psgi.version' => [ 1, 0 ],
105             'psgi.errors' => *STDERR,
106             'psgi.url_scheme' => 'http',
107             'psgi.nonblocking' => 1,
108             'psgi.streaming' => 1,
109             'psgi.multithread' => 0,
110             'psgi.multiprocess' => 1,
111             'psgi.run_once' => 0,
112             'psgix.input.buffered' => 1,
113             %$env,
114             };
115              
116             my $request_uri = $env->{REQUEST_URI};
117             my ( $file, $query_string ) = ( $request_uri =~ /([^?]*)(?:\?(.*))?/s ); # split at ?
118             $env->{PATH_INFO} = URI::Escape::uri_unescape $file;
119             $env->{QUERY_STRING} = $query_string || '';
120             $env->{SERVER_NAME} =~ s/:\d+$//;
121             # warn Dumper $env;
122              
123             my $self = {
124             stdin => '',
125             server => $server,
126             env => $env,
127             app => $app,
128             };
129             bless $self, ref $factory;
130             }
131              
132             # not support Async Input yet
133             sub stdin {
134             my ($self, $stdin, $is_eof) = @_;
135             $self->{stdin} .= $stdin;
136             if ($is_eof) {
137             open my $input, "<", \$self->{stdin};
138             $self->{env}->{'psgi.input'} = $input;
139             $self->run_app;
140             }
141             }
142              
143             sub run_app {
144             my $self = shift;
145             my $res = Plack::Util::run_app $self->{app}, $self->{env};
146              
147             if (ref $res eq 'ARRAY') {
148             $self->handle_response($res);
149             } elsif (ref $res eq 'CODE') {
150             $res->(sub { $self->handle_response($_[0]) });
151             } else {
152             Carp::croak("Unknown response: $res");
153             }
154             }
155              
156             sub handle_response {
157             my($self, $res) = @_;
158              
159             my $hdrs = "Status: $res->[0]\r\n";
160             Plack::Util::header_iter $res->[1], sub {
161             $hdrs .= "$_[0]: $_[1]\r\n";
162             };
163             $hdrs .= "\r\n";
164             $self->{server}->stdout($hdrs);
165              
166             if (defined $res->[2]) {
167             my $cb = sub { $self->{server}->stdout( $_[0] ) };
168             Plack::Util::foreach( $res->[2], $cb );
169             $self->{server}->stdout( "", 1 );
170             } else {
171             return Plack::Util::inline_object
172             write => sub { $self->{server}->stdout($_[0]) },
173             close => sub { $self->{server}->stdout("", 1) };
174             }
175             }
176              
177             package Plack::Handler::FCGI::EV;
178              
179             1;
180              
181             __END__
182              
183             =encoding utf-8
184              
185             =for stopwords
186              
187             =head1 NAME
188              
189             Plack::Handler::FCGI::EV - PSGI handler for FCGI::EV
190              
191             =head1 SYNOPSIS
192              
193             > plackup -s FCGI::EV --listen :8080 myapp.psgi
194              
195             =head1 DESCRIPTION
196              
197             Plack::Handler::FCGI::EV is an asynchronous PSGI handler using
198             L<FCGI::EV> as its backend.
199              
200             =head1 AUTHORS
201              
202             mala
203              
204             Tatsuhiko Miyagawa E<lt>miyagawa@bulknews.netE<gt>
205              
206             =head1 COPYRIGHT
207              
208             Copyright 2011- Tatsuhiko Miyagawa
209              
210             =head1 LICENSE
211              
212             This library is free software; you can redistribute it and/or modify
213             it under the same terms as Perl itself.
214              
215             =head1 SEE ALSO
216              
217             =cut