.\" -*- mode: troff; coding: utf-8 -*-
.\" Automatically generated by Pod::Man 5.01 (Pod::Simple 3.43)
.\"
.\" Standard preamble:
.\" ========================================================================
.de Sp \" Vertical space (when we can't use .PP)
.if t .sp .5v
.if n .sp
..
.de Vb \" Begin verbatim text
.ft CW
.nf
.ne \\$1
..
.de Ve \" End verbatim text
.ft R
.fi
..
.\" \*(C` and \*(C' are quotes in nroff, nothing in troff, for use with C<>.
.ie n \{\
. ds C` ""
. ds C' ""
'br\}
.el\{\
. ds C`
. ds C'
'br\}
.\"
.\" Escape single quotes in literal strings from groff's Unicode transform.
.ie \n(.g .ds Aq \(aq
.el .ds Aq '
.\"
.\" If the F register is >0, we'll generate index entries on stderr for
.\" titles (.TH), headers (.SH), subsections (.SS), items (.Ip), and index
.\" entries marked with X<> in POD. Of course, you'll have to process the
.\" output yourself in some meaningful fashion.
.\"
.\" Avoid warning from groff about undefined register 'F'.
.de IX
..
.nr rF 0
.if \n(.g .if rF .nr rF 1
.if (\n(rF:(\n(.g==0)) \{\
. if \nF \{\
. de IX
. tm Index:\\$1\t\\n%\t"\\$2"
..
. if !\nF==2 \{\
. nr % 0
. nr F 2
. \}
. \}
.\}
.rr rF
.\" ========================================================================
.\"
.IX Title "POE::Filter::HTTPD 3"
.TH POE::Filter::HTTPD 3 2024-07-13 "perl v5.38.2" "User Contributed Perl Documentation"
.\" For nroff, turn off justification. Always turn off hyphenation; it makes
.\" way too many mistakes in technical documents.
.if n .ad l
.nh
.SH NAME
POE::Filter::HTTPD \- parse simple HTTP requests, and serialize HTTP::Response
.SH SYNOPSIS
.IX Header "SYNOPSIS"
.Vb 1
\& #!perl
\&
\& use warnings;
\& use strict;
\&
\& use POE qw(Component::Server::TCP Filter::HTTPD);
\& use HTTP::Response;
\&
\& POE::Component::Server::TCP\->new(
\& Port => 8088,
\& ClientFilter => \*(AqPOE::Filter::HTTPD\*(Aq, ### <\-\- HERE WE ARE!
\&
\& ClientInput => sub {
\& my $request = $_[ARG0];
\&
\& # It\*(Aqs a response for the client if there was a problem.
\& if ($request\->isa("HTTP::Response")) {
\& my $response = $request;
\&
\& $request = $response\->request;
\& warn "ERROR: ", $request\->message if $request;
\&
\& $_[HEAP]{client}\->put($response);
\& $_[KERNEL]\->yield("shutdown");
\& return;
\& }
\&
\& my $request_fields = \*(Aq\*(Aq;
\& $request\->headers()\->scan(
\& sub {
\& my ($header, $value) = @_;
\& $request_fields .= (
\& "
$header | $value |
"
\& );
\& }
\& );
\&
\& my $response = HTTP::Response\->new(200);
\& $response\->push_header( \*(AqContent\-type\*(Aq, \*(Aqtext/html\*(Aq );
\& $response\->content(
\& "Your Request" .
\& "Details about your request:" .
\& "" .
\& ""
\& );
\&
\& $_[HEAP]{client}\->put($response);
\& $_[KERNEL]\->yield("shutdown");
\& }
\& );
\&
\& print "Aim your browser at port 8088 of this host.\en";
\& POE::Kernel\->run();
\& exit;
.Ve
.SH DESCRIPTION
.IX Header "DESCRIPTION"
POE::Filter::HTTPD interprets input streams as HTTP 0.9, 1.0 or 1.1
requests. It returns a HTTP::Request objects upon successfully
parsing a request.
.PP
On failure, it returns an HTTP::Response object describing the
failure. The intention is that application code will notice the
HTTP::Response and send it back without further processing. The
erroneous request object is sometimes available via the
"$r\->request" in HTTP::Response method. This is illustrated in the
"SYNOPSIS".
.PP
For output, POE::Filter::HTTPD accepts HTTP::Response objects and
returns their corresponding streams.
.PP
Please see HTTP::Request and HTTP::Response for details about
how to use these objects.
.PP
HTTP headers are not allowed to have UTF\-8 characters; they must be
ISO\-8859\-1. POE::Filter::HTTPD will convert all UTF\-8 into the MIME encoded
equivalent. It uses \f(CW\*(C`utf8::is_utf8\*(C'\fR for detection\-8 and
Email::MIME::RFC2047::Encoder for convertion. If utf8 is not
installed, no conversion happens. If Email::MIME::RFC2047::Encoder is
not installed, \f(CW\*(C`utf8::downgrade\*(C'\fR is used instead. In this last case, you will
see a warning if you try to send UTF\-8 headers.
.SH "PUBLIC FILTER METHODS"
.IX Header "PUBLIC FILTER METHODS"
POE::Filter::HTTPD implements the basic POE::Filter interface.
.SS new
.IX Subsection "new"
\&\fBnew()\fR accepts a list of named parameters.
.PP
\&\f(CW\*(C`MaxBuffer\*(C'\fR sets the maximum amount of data the filter will hold in memory.
Defaults to 512 MB (536870912 octets). Because POE::Filter::HTTPD copies
all data into memory, setting this number to high would allow a malicious
HTTPD client to fill all server memory and swap.
.PP
\&\f(CW\*(C`MaxContent\*(C'\fR sets the maximum size of the content of an HTTP request.
Defaults to 1 MB (1038336 octets). Because POE::Filter::HTTPD copies all
data into memory, setting this number to high would allow a malicious HTTPD
client to fill all server memory and swap. Ignored if "Streaming" is set.
.PP
\&\f(CW\*(C`Streaming\*(C'\fR turns on request streaming mode. Defaults to off. In
streaming mode this filter will return either an HTTP::Request object or a
block of content. The HTTP::Request object's content will return empty.
The blocks of content will be parts of the request's body, up to
Content-Length in size. You distinguish between request objects and content
blocks using \f(CW\*(C`Scalar::Util/bless\*(C'\fR (See "Streaming Request" below). This
option supersedes \f(CW\*(C`MaxContent\*(C'\fR.
.SH CAVEATS
.IX Header "CAVEATS"
Some versions of libwww are known to generate invalid HTTP. For
example, this code (adapted from the HTTP::Request::Common
documentation) will cause an error in a POE::Filter::HTTPD daemon:
.PP
NOTE: Using this test with libwww\-perl/5.834 showed that it added
the proper HTTP/1.1 data! We're not sure which version of LWP fixed
this. This example is valid for older LWP installations, beware!
.PP
.Vb 2
\& use HTTP::Request::Common;
\& use LWP::UserAgent;
\&
\& my $ua = LWP::UserAgent\->new();
\& $ua\->request(POST \*(Aqhttp://example.com\*(Aq, [ foo => \*(Aqbar\*(Aq ]);
.Ve
.PP
By default, HTTP::Request is HTTP version agnostic. It makes no
attempt to add an HTTP version header unless you specifically declare
a protocol using \f(CW\*(C`$request\->protocol(\*(AqHTTP/1.0\*(Aq)\*(C'\fR.
.PP
According to the HTTP 1.0 RFC (1945), when faced with no HTTP version
header, the parser is to default to HTTP/0.9. POE::Filter::HTTPD
follows this convention. In the transaction detailed above, the
Filter::HTTPD based daemon will return a 400 error since POST is not a
valid HTTP/0.9 request type.
.PP
Upon handling a request error, it is most expedient and reliable to
respond with the error and shut down the connection. Invalid HTTP
requests may corrupt the request stream. For example, the absence of
a Content-Length header signals that a request has no content.
Requests with content but without that header will be broken into a
content-less request and invalid data. The invalid data may also
appear to be a request! Hilarity will ensue, possibly repeatedly,
until the filter can find the next valid request. By shutting down
the connection on the first sign of error, the client can retry its
request with a clean connection and filter.
.SH "Streaming Request"
.IX Header "Streaming Request"
Normally POE::Filter::HTTPD reads the entire request content into memory
before returning the HTTP::Request to your code. In streaming mode, it will
return the content separately, as unblessed scalars. The content may be
split up into blocks of varying sizes, depending on OS and transport
constraints. Your code can distinguish the request object from the content
blocks using "blessed" in Scalar::Util.
.PP
.Vb 3
\& use Scalar::Util;
\& use POE::Wheel::ReadWrite;
\& use POE::Filter:HTTPD;
\&
\& $heap\->{wheel} = POE::Wheel::ReadWrite\->new(
\& InputEvent => \*(Aqhttp_input\*(Aq,
\& Filter => POE::Filter::HTTPD\->new( Streaming => 1 ),
\& # ....
\& );
\&
\& sub http_input_handler
\& {
\& my( $heap, $req_or_data ) = @_[ HEAP, ARG0 ];
\& if( blessed $req_or_data ) {
\& my $request = $req_or_data;
\& if( $request\->isa( \*(AqHTTP::Response\*(Aq) ) {
\& # HTTP error
\& $heap\->{wheel}\->put( $request );
\& }
\& else {
\& # HTTP request
\& # ....
\& }
\& }
\& else {
\& my $data = $req_or_data;
\& # ....
\& }
\& }
.Ve
.PP
You may trivally create a DoS bug if you hold all content in memory but do
not impose a maximum Content-Length. An attacker could send
\&\f(CW\*(C`Content\-Length: 1099511627776\*(C'\fR (aka 1 TB) and keep sending data until all
your system's memory and swap is filled.
.PP
Content-Length has been sanitized by POE::Filter::HTTPD so checking it is trivial :
.PP
.Vb 6
\& if( $request\->headers( \*(AqContent\-Length\*(Aq ) > 1024*1024 ) {
\& my $resp = HTTP::Response\->new( RC_REQUEST_ENTITY_TOO_LARGE ),
\& "So much content!" )
\& $heap\->{wheel}\->put( $resp );
\& return;
\& }
.Ve
.PP
If you want to handle large amounts of data, you should save the content to a file
before processing it. You still need to check Content-Length or an attacker might
fill up the partition.
.PP
.Vb 1
\& use File::Temp qw(tempfile);
\&
\& if( blessed $_[ARG0] ) {
\& $heap\->{request} = $_[ARG0];
\& if( $heap\->{request}\->method eq \*(AqGET\*(Aq ) {
\& handle_get( $heap );
\& delete $heap\->{request};
\& return;
\& }
\& my( $fh, $file ) = tempfile( "httpd\-XXXXXXXX", TMPDIR=>1 );
\& $heap\->{content_file} = $file;
\& $heap\->{content_fh} = $fh;
\& $heap\->{content_size} = 0;
\& }
\& else {
\& return unless $heap\->{request};
\&
\& $heap\->{content_size} += length( $_[ARG0] );
\& $heap\->{content_fh}\->print( $_[ARG0] );
\& if( $heap\->{content_size} >= $heap\->{request}\->headers( \*(Aqcontent\-length\*(Aq ) ) {
\& delete $heap\->{content_fh};
\& delete $heap\->{content_size};
\&
\& # Now we can parse $heap\->{content_file}
\& if( $heap\->{request}\->method eq \*(AqPOST\*(Aq ) {
\& handle_post( $heap );
\& }
\& else {
\& # error ...
\& }
\& }
\& }
\&
\& sub handle_post
\& {
\& my( $heap ) = @_;
\& # Now we have to load and parse $heap\->{content_file}
\&
\& # Next 6 lines make the data available to CGI\->init
\& local $ENV{REQUEST_METHOD} = \*(AqPOST\*(Aq;
\& local $CGI::PERLEX = $CGI::PERLEX = "CGI\-PerlEx/Fake";
\& local $ENV{CONTENT_TYPE} = $heap\->{req}\->header( \*(Aqcontent\-type\*(Aq );
\& local $ENV{CONTENT_LENGTH} = $heap\->{req}\->header( \*(Aqcontent\-length\*(Aq );
\& my $keep = IO::File\->new( "<&STDIN" ) or die "Unable to reopen STDIN: $!";
\& open STDIN, "<$heap\->{content_file}" or die "Reopening STDIN failed: $!";
\&
\& my $qcgi = CGI\->new();
\&
\& # cleanup
\& open STDIN, "<&".$keep\->fileno or die "Unable to reopen $keep: $!";
\& undef $keep;
\& unlink delete $heap\->{content_file};
\&
\& # now use $q as you would normaly
\& my $file = $q\->upload( \*(Aqfield_name\*(Aq );
\&
\& # ....
\& }
\&
\& sub handle_get
\& {
\& my( $heap ) = @_;
\&
\& # 4 lines to get data into CGI\->init
\& local $ENV{REQUEST_METHOD} = \*(AqGET\*(Aq;
\& local $CGI::PERLEX = $CGI::PERLEX = "CGI\-PerlEx/Fake";
\& local $ENV{CONTENT_TYPE} = $heap\->{req}\->header( \*(Aqcontent\-type\*(Aq );
\& local $ENV{\*(AqQUERY_STRING\*(Aq} = $heap\->{req}\->uri\->query;
\&
\& my $q = CGI\->new();
\&
\& # now use $q as you would normaly
\& # ....
\& }
.Ve
.SH "Streaming Response"
.IX Header "Streaming Response"
It is possible to use POE::Filter::HTTPD for streaming content, but an
application can use it to send headers and then switch to
POE::Filter::Stream.
.PP
From the input handler (the InputEvent handler if you're using wheels,
or the ClientInput handler for POE::Component::Server::TCP):
.PP
.Vb 4
\& my $response = HTTP::Response\->new(200);
\& $response\->push_header(\*(AqContent\-type\*(Aq, \*(Aqaudio/x\-mpeg\*(Aq);
\& $_[HEAP]{client}\->put($response);
\& $_[HEAP]{client}\->set_output_filter(POE::Filter::Stream\->new());
.Ve
.PP
Then the output-flushed handler (FlushEvent for POE::Wheel::ReadWrite,
or ClientFlushed for POE::Component::Server::TCP) can \fBput()\fR chunks of
the stream as needed.
.PP
.Vb 3
\& my $bytes_read = sysread(
\& $_[HEAP]{file_to_stream}, my $buffer = \*(Aq\*(Aq, 4096
\& );
\&
\& if ($bytes_read) {
\& $_[HEAP]{client}\->put($buffer);
\& }
\& else {
\& delete $_[HEAP]{file_to_stream};
\& $_[KERNEL]\->yield("shutdown");
\& }
.Ve
.SH "SEE ALSO"
.IX Header "SEE ALSO"
Please see POE::Filter for documentation regarding the base
interface.
.PP
The SEE ALSO section in POE contains a table of contents covering
the entire POE distribution.
.PP
HTTP::Request and HTTP::Response explain all the wonderful
things you can do with these classes.
.SH BUGS
.IX Header "BUGS"
Many aspects of HTTP 1.0 and higher are not supported, such as
keep-alive. A simple I/O filter can't support keep-alive, for
example. A number of more feature-rich POE HTTP servers are on the
CPAN. See
.SH "AUTHORS & COPYRIGHTS"
.IX Header "AUTHORS & COPYRIGHTS"
POE::Filter::HTTPD was contributed by Artur Bergman. Documentation is
provided by Rocco Caputo.
.PP
Please see POE for more information about authors and contributors.