File Coverage

blib/lib/FCGI/EV.pm
Criterion Covered Total %
statement 53 150 35.3
branch 0 50 0.0
condition 0 3 0.0
subroutine 18 25 72.0
pod 2 3 66.6
total 73 231 31.6


line stmt bran cond sub pod time code
1             package FCGI::EV;
2 1     1   52806 use 5.010001;
  1         3  
3 1     1   4 use warnings;
  1         2  
  1         18  
4 1     1   4 use strict;
  1         2  
  1         13  
5 1     1   465 use utf8;
  1         12  
  1         4  
6 1     1   23 use Carp;
  1         2  
  1         53  
7              
8             our $VERSION = 'v2.0.1';
9              
10 1     1   5 use Scalar::Util qw( weaken );
  1         1  
  1         30  
11 1     1   391 use IO::Stream;
  1         33798  
  1         7  
12              
13              
14 1     1   148 use constant FCGI_HEADER_LEN => 8;
  1         1  
  1         59  
15 1     1   5 use constant FCGI_VERSION_1 => 1;
  1         2  
  1         37  
16 1     1   9 use constant FCGI_BEGIN_REQUEST => 1;
  1         2  
  1         32  
17 1     1   5 use constant FCGI_END_REQUEST => 3;
  1         1  
  1         30  
18 1     1   4 use constant FCGI_PARAMS => 4;
  1         1  
  1         30  
19 1     1   3 use constant FCGI_STDIN => 5;
  1         1  
  1         27  
20 1     1   4 use constant FCGI_STDOUT => 6;
  1         1  
  1         28  
21 1     1   3 use constant FCGI_RESPONDER => 1;
  1         2  
  1         38  
22 1     1   4 use constant FCGI_REQUEST_COMPLETE => 0;
  1         2  
  1         50  
23 1         42 use constant END_REQUEST_COMPLETE =>
24 1     1   4 pack 'N C CCC', 0, FCGI_REQUEST_COMPLETE, 0, 0, 0;
  1         7  
25 1     1   4 use constant MAX_CONTENT_LEN => 0xFFFF;
  1         2  
  1         901  
26              
27              
28             sub new {
29 0     0 1   my ($class, $sock, $handler_class) = @_;
30 0           my $self = bless {
31             io => undef,
32             req_id => undef,
33             params => q{},
34             stdin_eof => undef,
35             handler => undef,
36             handler_class=>$handler_class,
37             }, $class;
38 0           $self->{io} = IO::Stream->new({
39             fh => $sock,
40             wait_for => IN|EOF,
41             cb => $self,
42             Wait_header => 1,
43             Need_in => FCGI_HEADER_LEN,
44             });
45 0           weaken($self->{io});
46             # It MAY have sense to add timeout between read() calls and timeout for
47             # overall time until EOF on STDIN will be received. First timeout
48             # can be about 3 minutes for slow clients, second can be about 4 hours
49             # for uploading huge files.
50 0           return;
51             }
52              
53             sub DESTROY {
54 0     0     my ($self) = @_;
55 0           $self->{handler} = undef; # call handler's DESTROY while $self is alive
56 0           return;
57             }
58              
59             sub stdout {
60 0     0 1   my ($self, $stdout, $is_eof) = @_;
61 0           my $io = $self->{io};
62 0 0         if (length $stdout) {
63 0           $io->{out_buf} .= _pack_pkt(FCGI_STDOUT, $self->{req_id}, $stdout);
64             }
65 0 0         if ($is_eof) {
66 0           $io->{out_buf} .= _pack_pkt(FCGI_STDOUT, $self->{req_id}, q{});
67 0           $io->{out_buf} .= _pack_pkt(FCGI_END_REQUEST, $self->{req_id}, END_REQUEST_COMPLETE);
68 0           $io->{wait_for} |= SENT;
69             }
70 0           $io->write();
71 0           return;
72             }
73              
74             sub IO {
75 0     0 0   my ($self, $io, $e, $err) = @_;
76 0 0         if ($err) {
77 0           warn "FCGI::EV: IO: $err\n";
78 0           return $io->close();
79             }
80 0 0         if ($e & EOF) {
81 0           return $io->close();
82             }
83 0 0         if ($e & SENT) {
84 0           return $io->close();
85             }
86 0           while (length $io->{in_buf} >= $io->{Need_in}) {
87 0 0         if ($io->{Wait_header}) {
88 0           $io->{Wait_header} = 0;
89 0           my ($content_len, $padding_len) = unpack 'x4 n C', $io->{in_buf};
90 0           $io->{Need_in} += $content_len + $padding_len;
91             }
92             else {
93 0           my $pkt = substr $io->{in_buf}, 0, $io->{Need_in}, q{};
94 0           $io->{Wait_header} = 1;
95 0           $io->{Need_in} = FCGI_HEADER_LEN;
96 0           my $error = $self->_process($pkt);
97 0 0         if ($error) {
98 0           warn "FCGI::EV: $error\n";
99 0           return $io->close();
100             }
101             }
102             }
103 0           return;
104             }
105              
106             sub _process {
107 0     0     my ($self, $pkt) = @_;
108 0           my ($ver, $type, $req_id, $content_len) = unpack 'C C n n', $pkt;
109 0           my $content = substr $pkt, FCGI_HEADER_LEN, $content_len;
110 0 0         if ($ver != FCGI_VERSION_1) {
111 0           return "unsupported version: $ver";
112             }
113 0 0 0       if (defined $self->{req_id} && $self->{req_id} != $req_id) {
114 0           return "unknown request id: $req_id";
115             }
116 0 0         if ($type == FCGI_BEGIN_REQUEST) {
    0          
    0          
117 0           my ($role) = unpack 'n', $content;
118 0 0         if ($role != FCGI_RESPONDER) {
119 0           return "role not supported: $role";
120             }
121 0 0         if (defined $self->{req_id}) {
122 0           return 'duplicated BEGIN_REQUEST';
123             }
124 0           $self->{req_id} = $req_id;
125             }
126             elsif ($type == FCGI_PARAMS) {
127 0 0         if ($self->{handler}) {
128 0           return 'got PARAMS for existing handler';
129             }
130 0 0         if (length $content) {
131 0           $self->{params} .= $content;
132             }
133             else {
134 0           my ($env, $err) = _unpack_nv($self->{params});
135 0 0         return $err if $err;
136 0           $self->{handler} = $self->{handler_class}->new($self, $env);
137             }
138             }
139             elsif ($type == FCGI_STDIN) {
140 0 0         if (!$self->{handler}) {
141 0           return 'got STDIN for non-existing handler';
142             }
143 0 0         if ($self->{stdin_eof}) {
144 0           return 'got STDIN after STDIN EOF';
145             }
146 0 0         if (length $content) {
147 0           $self->{handler}->stdin($content, 0);
148             }
149             else {
150 0           $self->{handler}->stdin(q{}, 1);
151 0           $self->{stdin_eof} = 1;
152             }
153             }
154             else {
155 0           return 'unknown type';
156             }
157 0           return;
158             }
159              
160             sub _unpack_nv {
161 0     0     my ($s) = @_;
162 0           my %nv;
163 0           while (length $s) {
164 0           my ($nlen, $vlen);
165 0           for my $len ($nlen, $vlen) {
166             ## no critic (ProhibitMagicNumbers)
167 0 0         return (undef, 'unpack_nv: not enough data') if length $s < 1;
168 0           ($len) = unpack 'C', $s;
169 0 0         if ($len & 0x80) {
170 0 0         return (undef, 'unpack_nv: not enough data') if length $s < 4;
171 0           ($len) = unpack 'N', $s;
172 0           $len &= 0x7FFFFFFF;
173 0           substr $s, 0, 4, q{};
174             }
175             else {
176 0           substr $s, 0, 1, q{};
177             }
178             ## use critic
179             }
180 0 0         return (undef, 'unpack_nv: not enough data') if length $s < $nlen + $vlen;
181 0           my $n = substr $s, 0, $nlen, q{};
182 0           my $v = substr $s, 0, $vlen, q{};
183 0           $nv{$n} = $v;
184             }
185 0           return (\%nv, undef);
186             }
187              
188             sub _pack_pkt {
189 0     0     my ($type, $req_id, $content) = @_;
190 0           $content = pack 'a*', $content; # convert from Unicode to UTF-8, if any
191 0           my $pkt = q{};
192 0           while (1) {
193 0           my $c = substr $content, 0, MAX_CONTENT_LEN, q{};
194 0           my $padding = q{};
195 0           $pkt .= pack 'CCnnCCa*a*',
196             FCGI_VERSION_1,
197             $type,
198             $req_id,
199             length $c,
200             length $padding,
201             0, # reserved
202             $c,
203             $padding,
204             ;
205 0 0         last if !length $content;
206             }
207 0           return $pkt;
208             }
209              
210              
211             1; # Magic true value required at end of module
212             __END__