1POE::Filter::HTTPD(3) User Contributed Perl DocumentationPOE::Filter::HTTPD(3)
2
3
4
6 POE::Filter::HTTPD - parse simple HTTP requests, and serialize
7 HTTP::Response
8
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
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
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
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
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
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
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
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
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 2023-01-20 POE::Filter::HTTPD(3)