home *** CD-ROM | disk | FTP | other *** search
/ PC Professionell 2004 December / PCpro_2004_12.ISO / files / webserver / xampp / xampp-perl-addon-1.4.9-installer.exe / HTTPD.pm < prev    next >
Encoding:
Perl POD Document  |  2004-01-26  |  11.7 KB  |  410 lines

  1. # $Id: HTTPD.pm,v 1.35 2004/01/26 00:20:06 sungo Exp $
  2.  
  3. # Filter::HTTPD Copyright 1998 Artur Bergman <artur@vogon.se>.
  4.  
  5. # Thanks go to Gisle Aas for his excellent HTTP::Daemon.  Some of the
  6. # get code was copied out if, unfournatly HTTP::Daemon is not easily
  7. # subclassed for POE because of the blocking nature.
  8.  
  9. # 2001-07-27 RCC: This filter will not support the newer get_one()
  10. # interface.  It gets single things by default, and it does not
  11. # support filter switching.  If someone absolutely needs to switch to
  12. # and from HTTPD filters, they should say so on POE's mailing list.
  13.  
  14. package POE::Filter::HTTPD;
  15. use POE::Preprocessor ( isa => "POE::Macro::UseBytes" );
  16.  
  17. use strict;
  18.  
  19. use vars qw($VERSION);
  20. $VERSION = do {my@r=(q$Revision: 1.35 $=~/\d+/g);sprintf"%d."."%04d"x$#r,@r};
  21.  
  22. use Carp qw(croak);
  23. use HTTP::Status;
  24. use HTTP::Request;
  25. use HTTP::Response;
  26. use HTTP::Date qw(time2str);
  27. use URI;
  28.  
  29. my $HTTP_1_0 = _http_version("HTTP/1.0");
  30. my $HTTP_1_1 = _http_version("HTTP/1.1");
  31.  
  32. #------------------------------------------------------------------------------
  33.  
  34. sub new {
  35.   my $type = shift;
  36.   my $self = { type   => 0,
  37.                buffer => '',
  38.                finish => 0,
  39.              };
  40.   bless $self, $type;
  41.   $self;
  42. }
  43.  
  44. #------------------------------------------------------------------------------
  45.  
  46. sub get {
  47.   my ($self, $stream) = @_;
  48.  
  49.   {% use_bytes %}
  50.  
  51.   local($_);
  52.  
  53.   # Sanity check.  "finish" is set when a request has completely
  54.   # arrived.  Subsequent get() calls on the same request should not
  55.   # happen.  -><- Maybe this should return [] instead of dying?
  56.  
  57.   if($self->{finish}) {
  58.  
  59.     # This works around a request length vs. actual content length
  60.     # error.  Looks like some browsers (mozilla!) sometimes add on an
  61.     # extra newline?
  62.  
  63.     # return [] unless @$stream and grep /\S/, @$stream;
  64.  
  65.     my @dump;
  66.     my $offset = 0;
  67.     $stream = join("", @$stream);
  68.     while (length $stream) {
  69.       my $line = substr($stream, 0, 16);
  70.       substr($stream, 0, 16) = '';
  71.  
  72.       my $hexdump  = unpack 'H*', $line;
  73.       $hexdump =~ s/(..)/$1 /g;
  74.  
  75.       $line =~ tr[ -~][.]c;
  76.       push @dump, sprintf( "%04x %-47.47s - %s\n", $offset, $hexdump, $line );
  77.       $offset += 16;
  78.     }
  79.  
  80.     return [ $self->build_error
  81.              ( RC_BAD_REQUEST,
  82.                "Did not want any more data.  Got this:" .
  83.                "<p><pre>" . join("", @dump) . "</pre></p>"
  84.              )
  85.            ];
  86.   }
  87.  
  88.   # Accumulate data in a framing buffer.
  89.  
  90.   $self->{buffer} .= join('', @$stream);
  91.  
  92.   # If headers were already received, then the framing buffer is
  93.   # purely content.  Return nothing until content-length bytes are in
  94.   # the buffer, then return the entire request.
  95.  
  96.   if($self->{header}) {
  97.     my $buf = $self->{buffer};
  98.     my $r   = $self->{header};
  99.     my $cl  = $r->content_length() || "0 (implicit)";
  100.     if (length($buf) >= $cl) {
  101.       $r->content($buf);
  102.       $self->{finish}++;
  103.       return [$r];
  104.     } else {
  105.       # print "$cl wanted, got " . length($buf) . "\n";
  106.     }
  107.     return [];
  108.   }
  109.  
  110.   # Headers aren't already received.  Short-circuit header parsing:
  111.   # don't return anything until we've received a blank line.
  112.  
  113.   return []
  114.     unless($self->{buffer} =~/(\x0D\x0A?\x0D\x0A?|\x0A\x0D?\x0A\x0D?)/s);
  115.  
  116.   # Copy the buffer for header parsing, and remove the header block
  117.   # from the content buffer.
  118.  
  119.   my $buf = $self->{buffer};
  120.   $self->{buffer} =~s/.*?(\x0D\x0A?\x0D\x0A?|\x0A\x0D?\x0A\x0D?)//s;
  121.  
  122.   # Parse the request line.
  123.  
  124.   if ($buf !~ s/^(\w+)[ \t]+(\S+)(?:[ \t]+(HTTP\/\d+\.\d+))?[^\012]*\012//) {
  125.     return [ $self->build_error(RC_BAD_REQUEST, "Request line parse failure.") ];
  126.   }
  127.   my $proto = $3 || "HTTP/0.9";
  128.  
  129.   # Use the request line to create a request object.
  130.  
  131.   my $r = HTTP::Request->new($1, URI->new($2));
  132.   $r->protocol($proto);
  133.   $self->{'httpd_client_proto'} = $proto = _http_version($proto);
  134.  
  135.   # Add the raw request's headers to the request object we'll be
  136.   # returning.
  137.  
  138.   if($proto >= $HTTP_1_0) {
  139.     my ($key,$val);
  140.   HEADER:
  141.     while ($buf =~ s/^([^\012]*)\012//) {
  142.       $_ = $1;
  143.       s/\015$//;
  144.       if (/^([\w\-~]+)\s*:\s*(.*)/) {
  145.         $r->push_header($key, $val) if $key;
  146.         ($key, $val) = ($1, $2);
  147.       } elsif (/^\s+(.*)/) {
  148.         $val .= " $1";
  149.       } else {
  150.         last HEADER;
  151.       }
  152.     }
  153.     $r->push_header($key,$val) if($key);
  154.   }
  155.  
  156.   $self->{header} = $r;
  157.  
  158.   # If this is a GET or HEAD request, we won't be expecting a message
  159.   # body.  Finish up.
  160.  
  161.   my $method = $r->method();
  162.   if ($method eq 'GET' or $method eq 'HEAD') {
  163.     $self->{finish}++;
  164.     return [$r];
  165.   }
  166.  
  167.   # However, if it's a POST request, check whether the entire content
  168.   # has already been received!  If so, add that to the request and
  169.   # we're done.  Otherwise we'll expect a subsequent get() call to
  170.   # finish things up.
  171.  
  172.   if($method eq 'POST') {
  173.  
  174. #    print "post:$buf:\END BUFFER\n";
  175. #    print length($buf)."-".$r->content_length()."\n";
  176.  
  177.     my $cl = $r->content_length();
  178.     unless(defined $cl) {
  179.         if($self->{'httpd_client_proto'} == 9) {
  180.             return [ $self->build_error(RC_BAD_REQUEST,  "POST request detected in an HTTP 0.9 transaction. POST is not a valid HTTP 0.9 transaction type. Please verify your HTTP version and transaction content.") ];
  181.  
  182.         } else { 
  183.             return [ $self->build_error(RC_LENGTH_REQUIRED, 
  184.                         "No content length found.") ];
  185.         }
  186.     }
  187.         
  188.     return [ $self->build_error(RC_BAD_REQUEST, "Content length contains non-digits.") ] 
  189.         unless $cl =~ /^\d+$/;
  190.  
  191.     if (length($buf) >= $cl) {
  192.       $r->content($buf);
  193.       $self->{finish}++;
  194.       return [$r];
  195.     }
  196.   }
  197.  
  198.   return [];
  199. }
  200.  
  201. #------------------------------------------------------------------------------
  202.  
  203. sub put {
  204.   my ($self, $responses) = @_;
  205.   my @raw;
  206.  
  207.   # HTTP::Response's as_string method returns the header lines
  208.   # terminated by "\n", which does not do the right thing if we want
  209.   # to send it to a client.  Here I've stolen HTTP::Response's
  210.   # as_string's code and altered it to use network newlines so picky
  211.   # browsers like lynx get what they expect.
  212.  
  213.   foreach (@$responses) {
  214.     my $code           = $_->code;
  215.     my $status_message = status_message($code) || "Unknown Error";
  216.     my $message        = $_->message  || "";
  217.     my $proto          = $_->protocol || 'HTTP/1.0';
  218.  
  219.     my $status_line = "$proto $code";
  220.     $status_line   .= " ($status_message)"  if $status_message ne $message;
  221.     $status_line   .= " $message";
  222.  
  223.     # Use network newlines, and be sure not to mangle newlines in the
  224.     # response's content.
  225.  
  226.     my @headers;
  227.     push @headers, $status_line;
  228.     push @headers, $_->headers_as_string("\x0D\x0A");
  229.  
  230.     push @raw, join("\x0D\x0A", @headers, "") . $_->content;
  231.   }
  232.  
  233.   \@raw;
  234. }
  235.  
  236. #------------------------------------------------------------------------------
  237.  
  238. sub get_pending {
  239.   my $self = shift;
  240.   croak ref($self)." does not support the get_pending() method\n";
  241.   return;
  242. }
  243.  
  244. #------------------------------------------------------------------------------
  245. # function specific to HTTPD;
  246. #------------------------------------------------------------------------------
  247.  
  248. # Internal function to parse an HTTP status line and return the HTTP
  249. # protocol version.
  250.  
  251. sub _http_version {
  252.   local($_) = shift;
  253.   return 0 unless m,^(?:HTTP/)?(\d+)\.(\d+)$,i;
  254.   $1 * 1000 + $2;
  255. }
  256.  
  257. # Build a basic response, given a status, a content type, and some
  258. # content.
  259.  
  260. sub build_basic_response {
  261.   my ($self, $content, $content_type, $status) = @_;
  262.  
  263.   {% use_bytes %}
  264.  
  265.   $content_type ||= 'text/html';
  266.   $status       ||= RC_OK;
  267.  
  268.   my $response = HTTP::Response->new($status);
  269.  
  270.   $response->push_header( 'Content-Type', $content_type );
  271.   $response->push_header( 'Content-Length', length($content) );
  272.   $response->content($content);
  273.  
  274.   return $response;
  275. }
  276.  
  277. sub build_error {
  278.   my($self, $status, $details) = @_;
  279.  
  280.   $status  ||= RC_BAD_REQUEST;
  281.   $details ||= '';
  282.   my $message = status_message($status) || "Unknown Error";
  283.  
  284.   return
  285.     $self->build_basic_response
  286.       ( ( "<html>" .
  287.           "<head>" .
  288.           "<title>Error $status: $message</title>" .
  289.           "</head>" .
  290.           "<body>" .
  291.           "<h1>Error $status: $message</h1>" .
  292.           "<p>$details</p>" .
  293.           "</body>" .
  294.           "</html>"
  295.         ),
  296.         "text/html",
  297.         $status
  298.       );
  299. }
  300.  
  301. ###############################################################################
  302. 1;
  303.  
  304. __END__
  305.  
  306. =head1 NAME
  307.  
  308. POE::Filter::HTTPD - convert stream to HTTP::Request; HTTP::Response to stream
  309.  
  310. =head1 SYNOPSIS
  311.  
  312.   $httpd = POE::Filter::HTTPD->new();
  313.   $arrayref_with_http_response_as_string =
  314.     $httpd->put($full_http_response_object);
  315.   $arrayref_with_http_request_object =
  316.     $line->get($arrayref_of_raw_data_chunks_from_driver);
  317.  
  318. =head1 DESCRIPTION
  319.  
  320. The HTTPD filter parses the first HTTP 1.0 request from an incoming
  321. stream into an HTTP::Request object (if the request is good) or an
  322. HTTP::Response object (if the request was malformed).  To send a
  323. response, give its put() method a HTTP::Response object.
  324.  
  325. Here is a sample input handler:
  326.  
  327.   sub got_request {
  328.     my ($heap, $request) = @_[HEAP, ARG0];
  329.  
  330.     # The Filter::HTTPD generated a response instead of a request.
  331.     # There must have been some kind of error.  You could also check
  332.     # (ref($request) eq 'HTTP::Response').
  333.     if ($request->isa('HTTP::Response')) {
  334.       $heap->{wheel}->put($request);
  335.       return;
  336.     }
  337.  
  338.     # Process the request here.
  339.     my $response = HTTP::Response->new(200);
  340.     $response->push_header( 'Content-Type', 'text/html' );
  341.     $response->content( $request->as_string() );
  342.  
  343.     $heap->{wheel}->put($response);
  344.   }
  345.  
  346. Please see the documentation for HTTP::Request and HTTP::Response.
  347.  
  348. =head1 PUBLIC FILTER METHODS
  349.  
  350. Please see POE::Filter.
  351.  
  352. =head1 CAVEATS
  353.  
  354. It is possible to generate invalid HTTP using libwww. This is specifically a
  355. problem if you are talking to a Filter::HTTPD driven daemon using libwww. For
  356. example, the following code (taken almost verbatim from the
  357. HTTP::Request::Common documentation) will cause an error in a Filter::HTTPD
  358. daemon:
  359.  
  360.     use HTTP::Request::Common;
  361.     use LWP::UserAgent;
  362.  
  363.     my $ua = LWP::UserAgent->new();
  364.     $ua->request(POST 'http://some/poe/driven/site', [ foo => 'bar' ]);
  365.  
  366. By default, HTTP::Request is HTTP version agnostic. It makes no attempt to add
  367. an HTTP version header unless you specifically declare a protocol using
  368. C<< $request->protocol('HTTP/1.0') >>. 
  369.  
  370. According to the HTTP 1.0 RFC (1945), when faced with no HTTP version header,
  371. the parser is to default to HTTP/0.9. Filter::HTTPD follows this convention. In
  372. the transaction detailed above, the Filter::HTTPD based daemon will return a 400
  373. error since POST is not a valid HTTP/0.9 request type.  
  374.  
  375. =head1 Streaming Media
  376.  
  377. It is perfectly possible to use Filter::HTTPD for streaming output
  378. media.  Even if it's not possible to change the input filter from
  379. Filter::HTTPD, by setting the output_filter to Filter::Stream and
  380. omitting any content in the HTTP::Response object.
  381.  
  382.   $wheel->put($response); # Without content, it sends just headers.
  383.   $wheel->set_output_filter(POE::Filter::Stream->new());
  384.   $wheel->put("Raw content.");
  385.  
  386. =head1 SEE ALSO
  387.  
  388. POE::Filter.
  389.  
  390. The SEE ALSO section in L<POE> contains a table of contents covering
  391. the entire POE distribution.
  392.  
  393. =head1 BUGS
  394.  
  395. =over 4
  396.  
  397. =item * Keep-alive is not supported.
  398.  
  399. =item * The full http 1.0 spec is not supported, specifically DELETE, LINK, and UNLINK.
  400.  
  401. =back
  402.  
  403. =head1 AUTHORS & COPYRIGHTS
  404.  
  405. The HTTPD filter was contributed by Artur Bergman.
  406.  
  407. Please see L<POE> for more information about authors and contributors.
  408.  
  409. =cut
  410.