📄 httpb.pm
字号:
# Code taken from Filter::HTTPD# Filter::HTTPD Copyright 1998 Artur Bergman <artur@vogon.se>.# Thanks go to Gisle Aas for his excellent HTTP::Daemon. Some of the# get code was copied out if, unfournatly HTTP::Daemon is not easily# subclassed for POE because of the blocking nature.# 2001-07-27 RCC: This filter will not support the newer get_one()# interface. It gets single things by default, and it does not# support filter switching. If someone absolutely needs to switch to# and from HTTPD filters, they should say so on POE's mailing list.package PunJab::Filter::HTTPB;use POE::Preprocessor ( isa => "POE::Macro::UseBytes" );use strict;use vars qw($VERSION);$VERSION = do {my@r=(q$Revision: 1.1 $=~/\d+/g);sprintf"%d."."%04d"x$#r,@r};use Carp qw(croak);use HTTP::Status;use HTTP::Request;use HTTP::Response;use HTTP::Date qw(time2str);use URI;use PunJab::Util;my $HTTP_1_0 = _http_version("HTTP/1.0");my $HTTP_1_1 = _http_version("HTTP/1.1");#------------------------------------------------------------------------------sub new { my $type = shift; my $self = { type => 0, buffer => '', finish => 0, count => 0, }; bless $self, $type; $self;}#------------------------------------------------------------------------------sub get { my ($self, $stream) = @_; {% use_bytes %} local($_); # Accumulate data in a framing buffer. $self->{buffer} .= join('', @$stream); # If headers were already received, then the framing buffer is # purely content. Return nothing until content-length bytes are in # the buffer, then return the entire request. if ($self->{finish}) { #warn $self->{buffer}; } if($self->{header}) { my $buf = $self->{buffer}; my $r = $self->{header}; my $cl = $r->content_length() || "0 (implicit)"; if (length($buf) >= $cl) { my $content = substr($buf,0,$cl); $r->content($content); $self->{finish}++; eval { $r->{'node'} = &str_to_node($buf); }; if ($@ or not defined $r->{'node'}) { return [ $self->build_error(RC_BAD_REQUEST, "Request line parse failure. ".$@) ]; } $self->{buffer}= substr($buf,$cl+1); delete $self->{header}; return [$r]; } else { #warn "$cl wanted, got " . length($buf) . "\n"; } return []; } # Headers aren't already received. Short-circuit header parsing: # don't return anything until we've received a blank line. return [] unless($self->{buffer} =~/(\x0D\x0A?\x0D\x0A?|\x0A\x0D?\x0A\x0D?)/s); # Copy the buffer for header parsing, and remove the header block # from the content buffer. my $buf = $self->{buffer}; $self->{buffer} =~s/.*?(\x0D\x0A?\x0D\x0A?|\x0A\x0D?\x0A\x0D?)//s; # Parse the request line. if ($buf !~ s/^(\w+)[ \t]+(\S+)(?:[ \t]+(HTTP\/\d+\.\d+))?[^\012]*\012//) { return [ $self->build_error(RC_BAD_REQUEST, "Request line parse failure.") ]; } my $proto = $3 || "HTTP/0.9"; # Use the request line to create a request object. my $r = HTTP::Request->new($1, URI->new($2)); $r->protocol($proto); $self->{'httpd_client_proto'} = $proto = _http_version($proto); # Add the raw request's headers to the request object we'll be # returning. if($proto >= $HTTP_1_0) { my ($key,$val); HEADER: while ($buf =~ s/^([^\012]*)\012//) { $_ = $1; s/\015$//; if (/^([\w\-~]+)\s*:\s*(.*)/) { $r->push_header($key, $val) if $key; ($key, $val) = ($1, $2); } elsif (/^\s+(.*)/) { $val .= " $1"; } else { last HEADER; } } $r->push_header($key,$val) if($key); } $self->{header} = $r; # If this is a GET or HEAD request, we won't be expecting a message # body. Finish up. my $method = $r->method(); if ($method eq 'GET' or $method eq 'HEAD') { #warn "Wrong Method\n"; $self->{finish}++; return [$r]; } # However, if it's a POST request, check whether the entire content # has already been received! If so, add that to the request and # we're done. Otherwise we'll expect a subsequent get() call to # finish things up. if($method eq 'POST') { #print "post:$buf:\nEND BUFFER\n"; #print length($buf)."-".$r->content_length()."\n"; my $cl = $r->content_length(); unless(defined $cl) { if($self->{'httpd_client_proto'} == 9) { 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.") ]; } else { return [ $self->build_error(RC_LENGTH_REQUIRED, "No content length found.") ]; } } return [ $self->build_error(RC_BAD_REQUEST, "Content length contains non-digits.") ] unless $cl =~ /^\d+$/; if (length($buf) >= $cl) { my $content = substr($buf,0,$cl); $r->content($content); eval { $r->{'node'} = &str_to_node($buf); }; if ($@ or not defined $r->{'node'}) { #warn $@; return [ $self->build_error(RC_BAD_REQUEST, "Request line parse failure. ".$@) ]; } $self->{buffer}= substr($buf,$cl+1); delete $self->{header}; $self->{finish}++; return [$r]; } } return [];}#------------------------------------------------------------------------------sub put { my ($self, $responses) = @_; my @raw; # HTTP::Response's as_string method returns the header lines # terminated by "\n", which does not do the right thing if we want # to send it to a client. Here I've stolen HTTP::Response's # as_string's code and altered it to use network newlines so picky # browsers like lynx get what they expect. foreach (@$responses) { my $code = $_->code; my $status_message = status_message($code) || "Unknown Error"; my $message = $_->message || ""; my $proto = $_->protocol || 'HTTP/1.0'; my $status_line = "$proto $code"; $status_line .= " ($status_message)" if $status_message ne $message; $status_line .= " $message"; # Use network newlines, and be sure not to mangle newlines in the # response's content. my @headers; push @headers, $status_line; push @headers, $_->headers_as_string("\x0D\x0A"); push @raw, join("\x0D\x0A", @headers, "") . $_->content; } \@raw;}#------------------------------------------------------------------------------sub get_pending { my $self = shift; croak ref($self)." does not support the get_pending() method\n"; return;}#------------------------------------------------------------------------------# function specific to HTTPD;#------------------------------------------------------------------------------# Internal function to parse an HTTP status line and return the HTTP# protocol version.sub _http_version { local($_) = shift; return 0 unless m,^(?:HTTP/)?(\d+)\.(\d+)$,i; $1 * 1000 + $2;}# Build a basic response, given a status, a content type, and some# content.sub build_basic_response { my ($self, $content, $content_type, $status) = @_; {% use_bytes %} $content_type ||= 'text/html'; $status ||= RC_OK; my $response = HTTP::Response->new($status); $response->push_header( 'Content-Type', $content_type ); $response->push_header( 'Content-Length', length($content) ); $response->content($content); return $response;}sub build_error { my($self, $status, $details) = @_; $status ||= RC_BAD_REQUEST; $details ||= ''; my $message = status_message($status) || "Unknown Error"; return $self->build_basic_response ( ( "<html>" . "<head>" . "<title>Error $status: $message</title>" . "</head>" . "<body>" . "<h1>Error $status: $message</h1>" . "<p>$details</p>" . "</body>" . "</html>" ), "text/html", $status );}###############################################################################1;__END__=head1 NAMEPOE::Filter::HTTPD - convert stream to HTTP::Request; HTTP::Response to stream=head1 SYNOPSIS $httpd = POE::Filter::HTTPD->new(); $arrayref_with_http_response_as_string = $httpd->put($full_http_response_object); $arrayref_with_http_request_object = $line->get($arrayref_of_raw_data_chunks_from_driver);=head1 DESCRIPTIONThe HTTPD filter parses the first HTTP 1.0 request from an incomingstream into an HTTP::Request object (if the request is good) or anHTTP::Response object (if the request was malformed). To send aresponse, give its put() method a HTTP::Response object.Here is a sample input handler: sub got_request { my ($heap, $request) = @_[HEAP, ARG0]; # The Filter::HTTPD generated a response instead of a request. # There must have been some kind of error. You could also check # (ref($request) eq 'HTTP::Response'). if ($request->isa('HTTP::Response')) { $heap->{wheel}->put($request); return; } # Process the request here. my $response = HTTP::Response->new(200); $response->push_header( 'Content-Type', 'text/html' ); $response->content( $request->as_string() ); $heap->{wheel}->put($response); }Please see the documentation for HTTP::Request and HTTP::Response.=head1 PUBLIC FILTER METHODSPlease see POE::Filter.=head1 CAVEATSIt is possible to generate invalid HTTP using libwww. This is specifically aproblem if you are talking to a Filter::HTTPD driven daemon using libwww. Forexample, the following code (taken almost verbatim from theHTTP::Request::Common documentation) will cause an error in a Filter::HTTPDdaemon: use HTTP::Request::Common; use LWP::UserAgent; my $ua = LWP::UserAgent->new(); $ua->request(POST 'http://some/poe/driven/site', [ foo => 'bar' ]);By default, HTTP::Request is HTTP version agnostic. It makes no attempt to addan HTTP version header unless you specifically declare a protocol usingC<< $request->protocol('HTTP/1.0') >>. According to the HTTP 1.0 RFC (1945), when faced with no HTTP version header,the parser is to default to HTTP/0.9. Filter::HTTPD follows this convention. Inthe transaction detailed above, the Filter::HTTPD based daemon will return a 400error since POST is not a valid HTTP/0.9 request type. =head1 Streaming MediaIt is perfectly possible to use Filter::HTTPD for streaming outputmedia. Even if it's not possible to change the input filter fromFilter::HTTPD, by setting the output_filter to Filter::Stream andomitting any content in the HTTP::Response object. $wheel->put($response); # Without content, it sends just headers. $wheel->set_output_filter(POE::Filter::Stream->new()); $wheel->put("Raw content.");=head1 SEE ALSOPOE::Filter.The SEE ALSO section in L<POE> contains a table of contents coveringthe entire POE distribution.=head1 BUGS=over 4=item * Keep-alive is not supported.=item * The full http 1.0 spec is not supported, specifically DELETE, LINK, and UNLINK.=back=head1 AUTHORS & COPYRIGHTSThe HTTPD filter was contributed by Artur Bergman.Please see L<POE> for more information about authors and contributors.=cut
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -