File Coverage

blib/lib/POE/Filter/HTTPHead.pm
Criterion Covered Total %
statement 103 116 88.7
branch 20 22 90.9
condition n/a
subroutine 16 16 100.0
pod 2 2 100.0
total 141 156 90.3


line stmt bran cond sub pod time code
1             package POE::Filter::HTTPHead_Line;
2             # vim: ts=2 sw=2 expandtab
3             $POE::Filter::HTTPHead_Line::VERSION = '0.949';
4 23     23   119916 use warnings;
  23         51  
  23         2375  
5 23     23   471 use strict;
  23         49  
  23         1348  
6              
7 23     23   353 use base 'POE::Filter';
  23         46  
  23         7799  
8              
9 23     23   2854 use HTTP::Response;
  23         75490  
  23         923  
10              
11             use constant {
12 23         2407 FRAMING_BUFFER => 0,
13             CURRENT_STATE => 1,
14             WORK_RESPONSE => 2,
15             PROTOCOL_VERSION => 3,
16 23     23   135 };
  23         58  
17              
18             use constant {
19 23         1392 STATE_STATUS => 0x01, # waiting for a status line
20             STATE_HEADER => 0x02, # gotten status, looking for header or end
21 23     23   120 };
  23         217  
22              
23 23     23   187 use constant DEBUG => 0;
  23         44  
  23         36365  
24              
25             sub new {
26 56     56   3091 my $type = shift;
27              
28 56         331 my $self = bless [
29             [], # FRAMING_BUFFER
30             STATE_STATUS, # CURRENT_STATE
31             undef, # WORK_RESPONSE
32             "0.9", # PROTOCOL_VERSION
33             ], $type;
34              
35 56         623 $self;
36             }
37              
38             sub get_one_start {
39 282     282   139273 my ($self, $chunks) = @_;
40              
41             # We're receiving newline-terminated lines. Strip off any carriage
42             # returns that might be left over.
43 282         1126 s/\x0D$// foreach @$chunks;
44 282         842 s/^\x0D// foreach @$chunks;
45              
46 282         416 push (@{$self->[FRAMING_BUFFER]}, @$chunks);
  282         994  
47             #warn "now got ", scalar @{$self->[FRAMING_BUFFER]}, " lines";
48             }
49              
50             sub get_one {
51 295     295   4460 my $self = shift;
52              
53             # Process lines while we have them.
54 295         382 LINE: while (@{$self->[FRAMING_BUFFER]}) {
  508         1428  
55 440         541 my $line = shift @{$self->[FRAMING_BUFFER]};
  440         776  
56              
57             # Waiting for a status line.
58 440 100       1251 if ($self->[CURRENT_STATE] == STATE_STATUS) {
59 65         97 DEBUG and warn "----- Waiting for a status line.\n";
60              
61             # Does the line look like a status line?
62 65 100       904 if ($line =~ m!^(\d{3})\s+(.+?)\s+HTTP/(\d+\.\d+)$!) {
    100          
    50          
    100          
    50          
63 16         101 $self->[PROTOCOL_VERSION] = $3;
64 16         144 $self->[WORK_RESPONSE] = HTTP::Response->new($1, $2);
65 16         1314 $self->[WORK_RESPONSE]->protocol('HTTP/' . $self->[PROTOCOL_VERSION]);
66 16         141 $self->[CURRENT_STATE] = STATE_HEADER;
67 16         27 DEBUG and warn "Got a status line";
68 16         41 next LINE;
69             }
70             elsif ($line =~ m!^(\d{3})\s+(.+?)$!) {
71 1         2 $self->[PROTOCOL_VERSION] = 0.9;
72 1         5 $self->[WORK_RESPONSE] = HTTP::Response->new($1, $2);
73 1         59 $self->[WORK_RESPONSE]->protocol('HTTP/' . $self->[PROTOCOL_VERSION]);
74 1         8 $self->[CURRENT_STATE] = STATE_HEADER;
75 1         1 DEBUG and warn "Got a status line";
76 1         2 next LINE;
77             }
78             elsif ($line =~ m!^(\d{3})$!) {
79 0         0 $self->[PROTOCOL_VERSION] = 0.9;
80 0         0 $self->[WORK_RESPONSE] = HTTP::Response->new($1);
81 0         0 $self->[WORK_RESPONSE]->protocol('HTTP/' . $self->[PROTOCOL_VERSION]);
82 0         0 $self->[CURRENT_STATE] = STATE_HEADER;
83 0         0 DEBUG and warn "Got a status line";
84 0         0 next LINE;
85             }
86             elsif ($line =~ m!^HTTP/(\d+\.\d+)\s+(\d{3})\s+(.*?)\s*$!) {
87 38         138 $self->[PROTOCOL_VERSION] = $1;
88 38         356 $self->[WORK_RESPONSE] = HTTP::Response->new($2, $3);
89 38         2705 $self->[WORK_RESPONSE]->protocol('HTTP/' . $self->[PROTOCOL_VERSION]);
90 38         325 $self->[CURRENT_STATE] = STATE_HEADER;
91 38         72 DEBUG and warn "Got a status line";
92 38         91 next LINE;
93             }
94             elsif ($line =~ m!^HTTP/(\d+\.\d+)\s+(\d{3})\s*$!) {
95 0         0 $self->[PROTOCOL_VERSION] = $1;
96 0         0 $self->[WORK_RESPONSE] = HTTP::Response->new($2);
97 0         0 $self->[WORK_RESPONSE]->protocol('HTTP/' . $self->[PROTOCOL_VERSION]);
98 0         0 $self->[CURRENT_STATE] = STATE_HEADER;
99 0         0 DEBUG and warn "Got a status line";
100 0         0 next LINE;
101             }
102              
103             # We have a line, but it doesn't look like a HTTP/1.1 status
104             # line. Assume it's an HTTP/0.9 response and fabricate headers.
105             # Also, put the line back. It's part of the content.
106 10         16 DEBUG and warn "Faking HTTP/0.9 headers (first line not status).\n";
107 10         66 my $resp = HTTP::Response->new (
108             '200', 'OK', ['Content-Type' => 'text/html'], $line
109             );
110 10         838 $resp->protocol('HTTP/0.9');
111             #unshift @{$self->[FRAMING_BUFFER]}, $line;
112 10         105 return [ $resp ];
113             }
114              
115             # A blank line signals the end of headers.
116 375 100       1338 if ($line =~ /^\s*$/) {
117 55         89 DEBUG and warn "Got a blank line. End of headers.\n";
118 55         97 $self->[CURRENT_STATE] = STATE_STATUS;
119 55         260 return [$self->[WORK_RESPONSE]];
120             }
121              
122             # We have a potential header line. Try to identify it's end.
123 320         404 my $i = 0;
124 320         621 CONTINUATION: while ($i < @{$self->[FRAMING_BUFFER]}) {
  328         890  
125             # Forward-looking line begins with whitespace. It's a
126             # continuation of the previous line.
127 166 100       529 $i++, next CONTINUATION if $self->[FRAMING_BUFFER]->[$i] =~ /^\s+\S/;
128              
129 158         174 DEBUG and warn "Found end of header ($i)\n";
130              
131             # Forward-looking line isn't a continuation line. All buffer
132             # lines before it are part of the current header.
133 158 100       345 if ($i) {
134 4         5 $line .= $_ foreach (
  4         13  
135 4         33 map { s/^\s+//; $_ }
  4         8  
136             splice(@{$self->[FRAMING_BUFFER]}, 0, $i)
137             );
138             }
139              
140 158         169 DEBUG and warn "Full header read: $line\n";
141              
142             # And parse the line.
143 158 100       774 if (
144             $line =~ m{
145             ^
146             ([^\x00-\x19()<>@,;:\\""\/\[\]\?={}\x20\t]+):
147             \s*([^\x00-\x07\x09-\x19]+)
148             $
149             }x
150             ) {
151 155         149 DEBUG and warn " header($1) value($2)\n";
152 155         1002 $self->[WORK_RESPONSE]->push_header($1, $2)
153             }
154              
155 158         11487 next LINE;
156             }
157              
158             # We didn't find a complete header. Put the line back, and wait
159             # for more input.
160 162         189 DEBUG and warn "Incomplete header. Waiting for more.\n";
161 162         191 unshift @{$self->[FRAMING_BUFFER]}, $line;
  162         534  
162 162         574 return [];
163             }
164              
165             # Didn't return anything else, so we don't have anything.
166 68         434 return [];
167             }
168              
169             #=for future
170             #
171             #sub put {
172             # my ($self, $responses) = @_;
173             # my $out;
174             #
175             # foreach my $response (@$responses) {
176             # $out = $response->as_string
177             # }
178             #
179             # $out;
180             #}
181             #
182             #=cut
183              
184             sub get_pending {
185 42     42   69 my $self = shift;
186 42         131 return $self->[FRAMING_BUFFER];
187             }
188              
189             package POE::Filter::HTTPHead;
190             $POE::Filter::HTTPHead::VERSION = '0.949';
191 23     23   152 use strict;
  23         47  
  23         1017  
192              
193             =head1 NAME
194              
195             POE::Filter::HTTPHead - filter data as HTTP::Response objects
196              
197             =head1 VERSION
198              
199             version 0.949
200              
201             =head1 SYNOPSYS
202              
203             $filter = POE::Filter::HTTPHead->new();
204             $arrayref_of_response_objects =
205             $filter->get($arrayref_of_raw_chunks_from_driver);
206              
207             $arrayref_of_leftovers = $filter->get_pending();
208              
209             =head1 DESCRIPTION
210              
211             The HTTPHead filter turns stream data that has the appropriate format
212             into a HTTP::Response object. In an all-POE world, this would sit on
213             the other end of a connection as L
214              
215             =cut
216              
217 23     23   137 use base qw(POE::Filter::Stackable);
  23         72  
  23         23909  
218 23     23   46154 use POE::Filter::Line;
  23         29031  
  23         4733  
219              
220             =head2 new
221              
222             Creates a new filter to parse HTTP headers. Takes no parameters, and
223             returns a shiny new POE::Filter::HTTPHead object.
224              
225             =cut
226              
227             sub new {
228 56     56 1 5417 my $type = shift;
229              
230             # Look for EOL defined as linefeed. We'll strip off possible
231             # carriage returns in HTTPHead_Line's get_one_start().
232              
233 56         469 my $self = $type->SUPER::new(
234             Filters => [
235             POE::Filter::Line->new(Literal => "\x0A"),
236             POE::Filter::HTTPHead_Line->new,
237             ],
238             );
239              
240 56         1229 return bless $self, $type;
241             }
242              
243             =head1 METHODS
244              
245             See L for documentation of the public API.
246              
247             =head2 get_pending
248              
249             Returns unparsed data pending in this filter's input buffer. It's
250             used by POE::Wheel objects to seamlessly switch between filters.
251              
252             Details may be found in the POE::Filter documentation.
253              
254             =cut
255              
256             sub get_pending {
257 42     42 1 2524 my $self = shift;
258              
259 42         73 my @pending = map {"$_\n"} @{$self->[0]->[1]->get_pending};
  0         0  
  42         178  
260 42         202 my $lines = $self->[0]->[0]->get_pending;
261 42 100       355 push (@pending, @$lines) if (defined $lines);
262              
263 42         161 return \@pending;
264             }
265              
266             #=for future?
267             #
268             #sub put {
269             # my $self = shift;
270             # return $self->[0]->[1]->put (@_);
271             #}
272             #
273             #=cut
274              
275             1;