File Coverage

blib/lib/POE/Filter/FastCGI.pm
Criterion Covered Total %
statement 39 137 28.4
branch 0 56 0.0
condition 0 6 0.0
subroutine 12 23 52.1
pod 0 6 0.0
total 51 228 22.3


line stmt bran cond sub pod time code
1             package POE::Filter::FastCGI;
2             $POE::Filter::FastCGI::VERSION = '0.20';
3 2     2   72246 use strict;
  2         5  
  2         52  
4 2     2   15 use bytes;
  2         4  
  2         11  
5              
6             our(@ROLE, @TYPE);
7              
8             BEGIN {
9             # Some normal constants
10 2     2   78 use constant FCGI_VERSION_1 => 1;
  2         3  
  2         115  
11 2     2   10 use constant HEADER_LENGTH => 8;
  2         3  
  2         76  
12 2     2   9 use constant STATE_WAIT => 1;
  2         2  
  2         101  
13 2     2   13 use constant STATE_DATA => 2;
  2         4  
  2         92  
14              
15 2     2   10 use constant REQUEST_COMPLETE => 0;
  2         4  
  2         81  
16 2     2   10 use constant CANT_MPX_CONN => 1;
  2         2  
  2         80  
17 2     2   10 use constant OVERLOADED => 2;
  2         3  
  2         96  
18 2     2   10 use constant UNKNOWN_ROLE => 3;
  2         3  
  2         103  
19              
20             # Request flag constants
21 2     2   11 use constant FCGI_KEEP_CONN => 1;
  2         3  
  2         271  
22              
23             # Constant maps
24 2     2   16 @TYPE = qw(
25             NULL
26             BEGIN_REQUEST
27             ABORT_REQUEST
28             END_REQUEST
29             PARAMS
30             FCGI_STDIN
31             FCGI_STDOUT
32             FCGI_STDERR
33             );
34 2         4 my $c = 1;
35 2         306 constant->import($_ => $c++) for @TYPE[1 .. $#TYPE];
36              
37 2         7 @ROLE = qw(
38             NULL
39             RESPONDER
40             AUTHORIZER
41             FILTER
42             );
43 2         4 $c = 1;
44 2         2407 constant->import($_ => $c++) for @ROLE[1 .. $#ROLE];
45             }
46              
47             sub new {
48 0     0 0   my($class) = @_;
49 0           my $self = bless {
50             buffer => "",
51             conn => [ ],
52             state => STATE_WAIT,
53             }, $class;
54 0           return $self;
55             }
56              
57             sub get {
58 0     0 0   my($self, $stream) = @_;
59 0           $self->get_one_start($stream);
60 0           my(@out, $conn);
61 0           do {
62 0           $conn = $self->get_one;
63 0 0         push @out => @$conn if @$conn;
64             }while(@$conn);
65 0           return \@out;
66             }
67              
68             sub get_pending {
69 0     0 0   my($self) = @_;
70 0 0         return $self->{buffer} ? $self->{buffer} : undef;
71             }
72              
73             sub get_one {
74 0     0 0   my($self) = @_;
75              
76 0           while($self->{buffer}) {
77 0 0         if($self->{state} == STATE_WAIT) {
78 0 0         return [ ] unless length $self->{buffer} >= HEADER_LENGTH;
79              
80             # Remove FastCGI header from buffer
81 0           my $header = substr $self->{buffer}, 0, HEADER_LENGTH, "";
82              
83 0           @$self{qw/version type requestid contentlen padlen/} =
84             unpack "CCnnC", $header;
85              
86             warn "Wrong version, or direct request from a browser"
87 0 0         if $self->{version} != FCGI_VERSION_1;
88              
89 0 0         if($self->{contentlen}) {
90 0           $self->{state} = STATE_DATA;
91             }else{
92 0           my $conn = $self->_do_record;
93 0 0         return [$conn] if defined $conn;
94 0           next;
95             }
96             }
97              
98 0 0         if(length $self->{buffer} >= ($self->{contentlen} + $self->{padlen})) {
99             # Remove content from buffer
100 0           my $content = substr $self->{buffer}, 0, $self->{contentlen}, "";
101             # Remove padding
102 0           substr $self->{buffer}, 0, $self->{padlen}, "";
103              
104 0           my $conn = $self->_do_record($content);
105 0 0         return [$conn] if defined $conn;
106             } else {
107 0           return [ ];
108             }
109             }
110 0           return [ ];
111             }
112              
113             sub get_one_start {
114 0     0 0   my($self, $stream) = @_;
115 0           $self->{buffer} .= join '', @$stream;
116             }
117              
118             # Process FastCGI record
119             sub _do_record {
120 0     0     my($self, $content) = @_;
121              
122 0 0         if($self->{type} == BEGIN_REQUEST) {
123 0           my($role, $flags) = unpack "nC", $content;
124 0           $self->{conn}->[$self->{requestid}] = {
125             state => BEGIN_REQUEST,
126             flags => $flags,
127             role => $ROLE[$role],
128             cgi => { },
129             };
130              
131 0 0         $self->{conn}->[$self->{requestid}]{keepconn} = $flags & FCGI_KEEP_CONN ? 1 : 0;
132 0           return $self->_cleanup;
133             }
134              
135 0 0         return $self->_cleanup if not defined $self->{conn}->[$self->{requestid}];
136              
137 0           my $conn = $self->{conn}->[$self->{requestid}];
138 0           $conn->{state} = $self->{type};
139              
140 0 0         if($self->{type} == PARAMS) {
    0          
141 0 0         if(defined $content) {
142 0           my $offset = 0;
143 0           my($nlen, $vlen);
144 0   0       while(defined($nlen = _read_nv_len(\$content, \$offset)) &&
145             defined($vlen = _read_nv_len(\$content, \$offset))) {
146 0           my($name, $value) = (substr($content, $offset, $nlen),
147             substr($content, $offset + $nlen, $vlen));
148 0           $conn->{cgi}->{$name} = $value;
149 0           $offset += $nlen + $vlen;
150             }
151             }
152             }elsif($self->{type} == FCGI_STDIN) {
153 0 0         if(defined $content) {
154 0           $conn->{postdata} .= $content;
155             }else{
156 0           my $cgi = delete $conn->{cgi};
157 0           return [$self->{requestid}, $conn, $cgi];
158             }
159             }
160              
161 0           return $self->_cleanup;
162             }
163              
164             sub _cleanup {
165 0     0     my($self, $request) = @_;
166 0           delete @$self{qw/version type requestid contentlen padlen/};
167 0           $self->{state} = STATE_WAIT;
168 0           return $request;
169             }
170              
171             sub _read_nv_len {
172 0     0     my($dataref, $offsetref) = @_;
173 0           my $buf = substr($$dataref, $$offsetref++, 1);
174 0 0         return undef unless length $buf;
175 0           my $len = unpack("C", $buf);
176              
177 0 0         if($len & 0x80) { # High order bit set
178 0           $buf = substr($$dataref, $$offsetref, 3);
179 0 0         return undef unless $buf;
180 0           $$offsetref += 3;
181 0           $len = unpack("N", (pack("C", $len & 0x7f) . $buf));
182             }
183              
184 0           return $len;
185             }
186              
187             sub put {
188 0     0 0   my($self, $input) = @_;
189 0           my @output;
190              
191 0           for my $response(@$input) {
192 0 0         if(UNIVERSAL::isa($response, "POE::Component::FastCGI::Response")) {
    0          
193             $self->_write(\@output, $response->{requestid},
194 0           FCGI_STDOUT, $response->as_string);
195 0           $self->_close(\@output, $response->{requestid});
196             }elsif(ref $response eq "HASH") {
197 0 0         if(length $response->{content}) {
198             $self->_write(\@output, $response->{requestid},
199 0           FCGI_STDOUT, $response->{content});
200             }
201 0 0 0       if(exists $response->{close} and $response->{close}) {
202 0           $self->_close(\@output, $response->{requestid});
203             }
204             }else{
205 0           warn "Unhandled put";
206             }
207             }
208              
209 0           return [ join '', @output ];
210             }
211              
212             # Close a connection
213             sub _close {
214 0     0     my($self, $output, $id, $status, $appstatus) = @_;
215 0 0         $status = REQUEST_COMPLETE unless defined $status;
216 0 0         $self->_write($output, $id, FCGI_STDOUT, "") if $status == REQUEST_COMPLETE;
217 0 0         $self->_write($output, $id, END_REQUEST,
218             pack("NCx3", (defined $appstatus ? $appstatus : 0), $status, 0));
219 0           delete $self->{conn}->[$id];
220             }
221              
222             # Append FastCGI packets to @$output.
223             sub _write {
224 0     0     my ($self, $output, $id, $type, $content) = @_;
225 0           my $length = length $content;
226 0           my $offset = 0;
227              
228 0 0         if($length == 0) {
229             # Null packet
230 0           push @$output, pack("CCnnCx", FCGI_VERSION_1, $type, $id, 0, 0);
231 0           return;
232             }
233              
234             # Create as many 32KiB packets as needed
235 0           while ($length > 0) {
236 0 0         my $len = $length > 32*1024 ? 32*1024 : $length;
237 0           my $padlen = (8 - ($len % 8)) % 8;
238 0           push @$output, pack("CCnnCxa${len}x$padlen",
239             FCGI_VERSION_1, $type, $id, $len, $padlen,
240             substr($content, $offset, $len));
241              
242 0           $length -= $len;
243 0           $offset += $len;
244             }
245             }
246              
247             1;
248              
249             =head1 NAME
250              
251             POE::Filter::FastCGI - Parse and create FastCGI requests
252              
253             =head1 SYNOPSIS
254              
255             $fastcgi = POE::Filter::FastCGI->new;
256             $arrayref_with_binary_fastcgi_response = $fastcgi->put($put);
257             $arrayref_with_fastcgi_request_array = $fastcgi->get($chunks);
258              
259             =head1 DESCRIPTION
260              
261             Parses the FastCGI binary protocol into a perl array with the CGI
262             environment and any POST or other data that is sent.
263              
264             Accepts either L objects or a
265             simple hash reference via C and converts into the FastCGI
266             binary protocol. The hash reference should have keys of requestid
267             and content and an optional key of close to end the FastCGI
268             request.
269              
270             =head1 AUTHOR
271              
272             Copyright 2005, David Leadbeater L. All rights reserved.
273              
274             This library is free software; you can redistribute it and/or modify
275             it under the same terms as Perl itself.
276              
277             Some parts taken from FCGI's Pure Perl implementation.
278              
279             =head1 BUGS
280              
281             This is rather tightly coupled with L, ideally
282             there would be some form of intermediate perl object to use for FastCGI
283             like L can make use of L.
284              
285             This code is pure perl, it's probably slow compared to L (which is
286             mostly C) and it doesn't handle as many record types as L. However
287             L doesn't allow more than one concurrent request.
288              
289             =head1 SEE ALSO
290              
291             L, L, L.
292              
293             =cut