1POE::Filter::HTTPD(3) User Contributed Perl DocumentationPOE::Filter::HTTPD(3)
2
3
4

NAME

6       POE::Filter::HTTPD - parse simple HTTP requests, and serialize
7       HTTP::Response
8

SYNOPSIS

10         #!perl
11
12         use warnings;
13         use strict;
14
15         use POE qw(Component::Server::TCP Filter::HTTPD);
16         use HTTP::Response;
17
18         POE::Component::Server::TCP->new(
19           Port         => 8088,
20           ClientFilter => 'POE::Filter::HTTPD',  ### <-- HERE WE ARE!
21
22           ClientInput => sub {
23             my $request = $_[ARG0];
24
25             # It's a response for the client if there was a problem.
26             if ($request->isa("HTTP::Response")) {
27               my $response = $request;
28
29               $request = $response->request;
30               warn "ERROR: ", $request->message if $request;
31
32               $_[HEAP]{client}->put($response);
33               $_[KERNEL]->yield("shutdown");
34               return;
35             }
36
37             my $request_fields = '';
38             $request->headers()->scan(
39               sub {
40                 my ($header, $value) = @_;
41                 $request_fields .= (
42                   "<tr><td>$header</td><td>$value</td></tr>"
43                 );
44               }
45             );
46
47             my $response = HTTP::Response->new(200);
48             $response->push_header( 'Content-type', 'text/html' );
49             $response->content(
50               "<html><head><title>Your Request</title></head>" .
51               "<body>Details about your request:" .
52               "<table border='1'>$request_fields</table>" .
53               "</body></html>"
54             );
55
56             $_[HEAP]{client}->put($response);
57             $_[KERNEL]->yield("shutdown");
58           }
59         );
60
61         print "Aim your browser at port 8088 of this host.\n";
62         POE::Kernel->run();
63         exit;
64

DESCRIPTION

66       POE::Filter::HTTPD interprets input streams as HTTP 0.9, 1.0 or 1.1
67       requests.  It returns a HTTP::Request objects upon successfully parsing
68       a request.
69
70       On failure, it returns an HTTP::Response object describing the failure.
71       The intention is that application code will notice the HTTP::Response
72       and send it back without further processing. The erroneous request
73       object is sometimes available via the "$r->request" in HTTP::Response
74       method.  This is illustrated in the "SYNOPSIS".
75
76       For output, POE::Filter::HTTPD accepts HTTP::Response objects and
77       returns their corresponding streams.
78
79       Please see HTTP::Request and HTTP::Response for details about how to
80       use these objects.
81
82       HTTP headers are not allowed to have UTF-8 characters; they must be
83       ISO-8859-1.  POE::Filter::HTTPD will convert all UTF-8 into the MIME
84       encoded equivalent.  It uses "utf8::is_utf8" for detection-8 and
85       Email::MIME::RFC2047::Encoder for convertion.  If utf8 is not
86       installed, no conversion happens.  If Email::MIME::RFC2047::Encoder is
87       not installed, "utf8::downgrade" is used instead.  In this last case,
88       you will see a warning if you try to send UTF-8 headers.
89

PUBLIC FILTER METHODS

91       POE::Filter::HTTPD implements the basic POE::Filter interface.
92
93   new
94       new() accepts a list of named parameters.
95
96       "MaxBuffer" sets the maximum amount of data the filter will hold in
97       memory.  Defaults to 512 MB (536870912 octets).  Because
98       POE::Filter::HTTPD copies all data into memory, setting this number to
99       high would allow a malicious HTTPD client to fill all server memory and
100       swap.
101
102       "MaxContent" sets the maximum size of the content of an HTTP request.
103       Defaults to 1 MB (1038336 octets).  Because POE::Filter::HTTPD copies
104       all data into memory, setting this number to high would allow a
105       malicious HTTPD client to fill all server memory and swap.  Ignored if
106       "Streaming" is set.
107
108       "Streaming" turns on request streaming mode.  Defaults to off.  In
109       streaming mode this filter will return either an HTTP::Request object
110       or a block of content.  The HTTP::Request object's content will return
111       empty.  The blocks of content will be parts of the request's body, up
112       to Content-Length in size.  You distinguish between request objects and
113       content blocks using "Scalar::Util/bless" (See "Streaming Request"
114       below).  This option supersedes "MaxContent".
115

CAVEATS

117       Some versions of libwww are known to generate invalid HTTP.  For
118       example, this code (adapted from the HTTP::Request::Common
119       documentation) will cause an error in a POE::Filter::HTTPD daemon:
120
121       NOTE: Using this test with libwww-perl/5.834 showed that it added the
122       proper HTTP/1.1 data! We're not sure which version of LWP fixed this.
123       This example is valid for older LWP installations, beware!
124
125         use HTTP::Request::Common;
126         use LWP::UserAgent;
127
128         my $ua = LWP::UserAgent->new();
129         $ua->request(POST 'http://example.com', [ foo => 'bar' ]);
130
131       By default, HTTP::Request is HTTP version agnostic. It makes no attempt
132       to add an HTTP version header unless you specifically declare a
133       protocol using "$request->protocol('HTTP/1.0')".
134
135       According to the HTTP 1.0 RFC (1945), when faced with no HTTP version
136       header, the parser is to default to HTTP/0.9.  POE::Filter::HTTPD
137       follows this convention.  In the transaction detailed above, the
138       Filter::HTTPD based daemon will return a 400 error since POST is not a
139       valid HTTP/0.9 request type.
140
141       Upon handling a request error, it is most expedient and reliable to
142       respond with the error and shut down the connection.  Invalid HTTP
143       requests may corrupt the request stream.  For example, the absence of a
144       Content-Length header signals that a request has no content.  Requests
145       with content but without that header will be broken into a content-less
146       request and invalid data.  The invalid data may also appear to be a
147       request!  Hilarity will ensue, possibly repeatedly, until the filter
148       can find the next valid request.  By shutting down the connection on
149       the first sign of error, the client can retry its request with a clean
150       connection and filter.
151

Streaming Request

153       Normally POE::Filter::HTTPD reads the entire request content into
154       memory before returning the HTTP::Request to your code.  In streaming
155       mode, it will return the content separately, as unblessed scalars.  The
156       content may be split up into blocks of varying sizes, depending on OS
157       and transport constraints.  Your code can distinguish the request
158       object from the content blocks using "blessed" in Scalar::Util.
159
160           use Scalar::Util;
161           use POE::Wheel::ReadWrite;
162           use POE::Filter:HTTPD;
163
164           $heap->{wheel} = POE::Wheel::ReadWrite->new(
165                               InputEvent => 'http_input',
166                               Filter => POE::Filter::HTTPD->new( Streaming => 1 ),
167                               # ....
168                       );
169
170           sub http_input_handler
171           {
172               my( $heap, $req_or_data ) = @_[ HEAP, ARG0 ];
173               if( blessed $req_or_data ) {
174                   my $request = $req_or_data;
175                   if( $request->isa( 'HTTP::Response') ) {
176                       # HTTP error
177                       $heap->{wheel}->put( $request );
178                   }
179                   else {
180                       # HTTP request
181                       # ....
182                   }
183               }
184               else {
185                   my $data = $req_or_data;
186                   # ....
187               }
188           }
189
190       You may trivally create a DoS bug if you hold all content in memory but
191       do not impose a maximum Content-Length.  An attacker could send
192       "Content-Length: 1099511627776" (aka 1 TB) and keep sending data until
193       all your system's memory and swap is filled.
194
195       Content-Length has been sanitized by POE::Filter::HTTPD so checking it
196       is trivial :
197
198           if( $request->headers( 'Content-Length' ) > 1024*1024 ) {
199               my $resp = HTTP::Response->new( RC_REQUEST_ENTITY_TOO_LARGE ),
200                                                    "So much content!" )
201               $heap->{wheel}->put( $resp );
202               return;
203           }
204
205       If you want to handle large amounts of data, you should save the
206       content to a file before processing it.  You still need to check
207       Content-Length or an attacker might fill up the partition.
208
209           use File::Temp qw(tempfile);
210
211           if( blessed $_[ARG0] ) {
212               $heap->{request} = $_[ARG0];
213               if( $heap->{request}->method eq 'GET' ) {
214                   handle_get( $heap );
215                   delete $heap->{request};
216                   return;
217               }
218               my( $fh, $file ) = tempfile( "httpd-XXXXXXXX", TMPDIR=>1 );
219               $heap->{content_file} = $file;
220               $heap->{content_fh} = $fh;
221               $heap->{content_size} = 0;
222           }
223           else {
224               return unless $heap->{request};
225
226               $heap->{content_size} += length( $_[ARG0] );
227               $heap->{content_fh}->print( $_[ARG0] );
228               if( $heap->{content_size} >= $heap->{request}->headers( 'content-length' ) ) {
229                   delete $heap->{content_fh};
230                   delete $heap->{content_size};
231
232                   # Now we can parse $heap->{content_file}
233                   if( $heap->{request}->method eq 'POST' ) {
234                       handle_post( $heap );
235                   }
236                   else {
237                       # error ...
238                   }
239               }
240           }
241
242           sub handle_post
243           {
244               my( $heap ) = @_;
245               # Now we have to load and parse $heap->{content_file}
246
247               # Next 6 lines make the data available to CGI->init
248               local $ENV{REQUEST_METHOD} = 'POST';
249               local $CGI::PERLEX = $CGI::PERLEX = "CGI-PerlEx/Fake";
250               local $ENV{CONTENT_TYPE} = $heap->{req}->header( 'content-type' );
251               local $ENV{CONTENT_LENGTH} = $heap->{req}->header( 'content-length' );
252               my $keep = IO::File->new( "<&STDIN" ) or die "Unable to reopen STDIN: $!";
253               open STDIN, "<$heap->{content_file}" or die "Reopening STDIN failed: $!";
254
255               my $qcgi = CGI->new();
256
257               # cleanup
258               open STDIN, "<&".$keep->fileno or die "Unable to reopen $keep: $!";
259               undef $keep;
260               unlink delete $heap->{content_file};
261
262               # now use $q as you would normaly
263               my $file = $q->upload( 'field_name' );
264
265               # ....
266           }
267
268           sub handle_get
269           {
270               my( $heap ) = @_;
271
272               # 4 lines to get data into CGI->init
273               local $ENV{REQUEST_METHOD} = 'GET';
274               local $CGI::PERLEX = $CGI::PERLEX = "CGI-PerlEx/Fake";
275               local $ENV{CONTENT_TYPE} = $heap->{req}->header( 'content-type' );
276               local $ENV{'QUERY_STRING'} = $heap->{req}->uri->query;
277
278               my $q = CGI->new();
279
280               # now use $q as you would normaly
281               # ....
282           }
283

Streaming Response

285       It is possible to use POE::Filter::HTTPD for streaming content, but an
286       application can use it to send headers and then switch to
287       POE::Filter::Stream.
288
289       From the input handler (the InputEvent handler if you're using wheels,
290       or the ClientInput handler for POE::Component::Server::TCP):
291
292         my $response = HTTP::Response->new(200);
293         $response->push_header('Content-type', 'audio/x-mpeg');
294         $_[HEAP]{client}->put($response);
295         $_[HEAP]{client}->set_output_filter(POE::Filter::Stream->new());
296
297       Then the output-flushed handler (FlushEvent for POE::Wheel::ReadWrite,
298       or ClientFlushed for POE::Component::Server::TCP) can put() chunks of
299       the stream as needed.
300
301         my $bytes_read = sysread(
302           $_[HEAP]{file_to_stream}, my $buffer = '', 4096
303         );
304
305         if ($bytes_read) {
306           $_[HEAP]{client}->put($buffer);
307         }
308         else {
309           delete $_[HEAP]{file_to_stream};
310           $_[KERNEL]->yield("shutdown");
311         }
312

SEE ALSO

314       Please see POE::Filter for documentation regarding the base interface.
315
316       The SEE ALSO section in POE contains a table of contents covering the
317       entire POE distribution.
318
319       HTTP::Request and HTTP::Response explain all the wonderful things you
320       can do with these classes.
321

BUGS

323       Many aspects of HTTP 1.0 and higher are not supported, such as keep-
324       alive.  A simple I/O filter can't support keep-alive, for example.  A
325       number of more feature-rich POE HTTP servers are on the CPAN.  See
326       <http://search.cpan.org/search?query=POE+http+server&mode=dist>
327

AUTHORS & COPYRIGHTS

329       POE::Filter::HTTPD was contributed by Artur Bergman.  Documentation is
330       provided by Rocco Caputo.
331
332       Please see POE for more information about authors and contributors.
333
334
335
336perl v5.36.0                      2022-07-22             POE::Filter::HTTPD(3)
Impressum