POE-Component-Server-HTTP-0.09/0002755000175000017500000000000010434672213016172 5ustar richardcrichardcPOE-Component-Server-HTTP-0.09/t/0002755000175000017500000000000010434672213016435 5ustar richardcrichardcPOE-Component-Server-HTTP-0.09/t/last.gif0000644000175000017500000000206210434666073020074 0ustar richardcrichardcGIF89a2[y 6%Mw@c,2I8ͻ`(dihlp,tmx|pH,Ȥrl:ШtJZجvzxL.zn_`3s8TH}{~(aB > `KD u+ ܩߙ  i'p8`œ`M)OQe)Y/1'[W_3spټL^*K0Z=B)=K੝^f`(ٔO~ݚQDk2+z \C; jxEH*MT z_[n'U_֯dTzޙn\1rC\p_*tSKwҞ>LsT6@w &AuڅL24™ndb,厏 y*HcI%pd${NUrxB!Gq3ND50 VLC8j(=='V wE5OatCAxf^XS1^Θ2XL0x_ңp!Ce]KW-9j`I#b#G|Kp9۬SYD‡74hʛKjc̒MJro.R?c v'ǰɂV=@;3 KB%Xg\w`-dmhlp-tmx|߀.^E;POE-Component-Server-HTTP-0.09/t/first.txt0000644000175000017500000000001610434666073020327 0ustar richardcrichardcPlease wait.. POE-Component-Server-HTTP-0.09/t/last.txt0000644000175000017500000000005110434666073020142 0ustar richardcrichardcIf you can read this, everything worked. POE-Component-Server-HTTP-0.09/t/30_error.t0000644000175000017500000001433610434666073020271 0ustar richardcrichardc#!/usr/bin/perl -w use strict; use Test::More; #sub POE::Kernel::TRACE_EVENTS {1} sub POE::Kernel::ASSERT_EVENTS {1} use LWP::UserAgent; use HTTP::Request; use POE::Kernel; use POE::Component::Server::HTTP; use IO::Socket::INET; use POE::API::Peek; sub DEBUG { 0 }; my $PORT=2080; my $pid=fork; die "Unable to fork: $!" unless defined $pid; END { if($pid) { kill 2, $pid or ($!==3) or warn "Unable to kill $pid: $!"; } } #################################################################### unless ($pid) { # we are child Test::Builder->new->no_ending(1); # stop kernel from griping ${$poe_kernel->[POE::Kernel::KR_RUN]} |= POE::Kernel::KR_RUN_CALLED; print STDERR "$$: Sleep 2..."; sleep 2; print STDERR "continue\n"; ############################ # 1, 2, 3 my $sock=IO::Socket::INET->new(PeerAddr=>'localhost', PeerPort=>$PORT); $sock or die "Unable to connect to localhost:$PORT: $!"; $sock->close; # taunt other end ############################ # 4, 5, 6 $sock=IO::Socket::INET->new(PeerAddr=>'localhost', PeerPort=>$PORT); $sock or die "Unable to connect to localhost:$PORT: $!"; my $req=HTTP::Request->new(GET => "http://localhost:$PORT/"); $sock->print(join ' ', $req->method, $req->uri->as_string, "0\n"); sleep 1; $sock->close; # taunt other end ############################ # 7, 8, 9 $sock=IO::Socket::INET->new(PeerAddr=>'localhost', PeerPort=>$PORT); $sock or die "Unable to connect to localhost:$PORT: $!"; $req=HTTP::Request->new(GET => "http://localhost:$PORT/honk"); $sock->print($req->as_string); $sock->close; # taunt other end ############################ # 10, 11 $req=HTTP::Request->new(GET => "http://localhost:$PORT/honk/shutdown.html"); my $UA = LWP::UserAgent->new; my $resp=$UA->request($req); exit 0; } #################################################################### # we are the parent plan tests=>11; my $Q=1; my $shutdown=0; my $top=0; my $bonk=0; my $aliases = POE::Component::Server::HTTP->new( Port => $PORT, Address=>'localhost', ContentHandler => { '/' => \&top, '/honk/shutdown.html' => \&shutdown, '/bonk/' => \&bonk }, PostHandler => { '/' => \&post_top, '/honk/shutdown.html' => \&post_shutdown, }, ErrorHandler => { '/' => \&error }, Headers => { Server => 'TestServer' }, ); POE::Session->create( inline_states => { _start => sub { $poe_kernel->alias_set('HONK'); $poe_kernel->sig(USR1=>'usr1'); $poe_kernel->sig(USR2=>'usr2'); }, usr1=>sub {__peek(0)}, usr2=>sub {__peek(1)}, }); $poe_kernel->run; ####################################### sub error { my ($request, $response) = @_; DEBUG and __peek(1); die "Why is Q=$Q" unless $Q; ok(($request->is_error and $response->is_error), "this is an error"); my $op=$request->header('Operation'); my $errstr=$request->header('Error'); my $errnum=$request->header('Errnum'); DEBUG and warn "$$: ERROR op=$op errnum=$errnum errstr=$errstr\n"; if($Q <= 3) { ok(($op eq 'read' and $errnum == 0), "closed connection") or die "Why did i get this error? op=$op errnum=$errnum errstr=$errstr"; } else { die "Whoah!"; } $Q++; return RC_OK; } ####################################### sub top { my ($request, $response) = @_; $response->code(RC_OK); $response->content_type('text/plain'); $response->content("this is top"); $top=1; return RC_OK; } ####################################### sub bonk { my ($request, $response) = @_; $response->code(RC_OK); $response->content_type('text/plain'); $response->content("this is bonk"); $bonk=1; return RC_OK; } ####################################### sub post_top { my($request, $response)=@_; ok(($shutdown or (not $bonk and $request->is_error)), "all but shutdown requests should be errors"); } ####################################### sub post_shutdown { my($request, $response)=@_; ok($shutdown, "we are after shutdown"); } ####################################### sub shutdown { my ($request, $response) = @_; DEBUG and warn "SHUTDOWN"; $poe_kernel->post($aliases->{httpd} => 'shutdown'); $poe_kernel->post($aliases->{tcp} => 'shutdown'); $shutdown=1; $response->code(RC_OK); $response->content_type('text/plain'); $response->content("going to shutdown"); return RC_OK; } sub __peek { my($verbose)=@_; my $api=POE::API::Peek->new(); my @queue = $api->event_queue_dump(); my $ret = "Event Queue:\n"; foreach my $item (@queue) { $ret .= "\t* ID: ". $item->{ID}." - Index: ".$item->{index}."\n"; $ret .= "\t\tPriority: ".$item->{priority}."\n"; $ret .= "\t\tEvent: ".$item->{event}."\n"; if($verbose) { $ret .= "\t\tSource: ". $api->session_id_loggable($item->{source}). "\n"; $ret .= "\t\tDestination: ". $api->session_id_loggable($item->{destination}). "\n"; $ret .= "\t\tType: ".$item->{type}."\n"; $ret .= "\n"; } } if($verbose) { $ret.="Sessions: \n" if $api->session_count; foreach my $session ($api->session_list) { $ret.="\tSession ".$api->session_id_loggable($session)." ($session)"; $ret.="\n\t\tref count: ".$api->get_session_refcount($session); $ret.="\n"; my $q=$api->get_session_extref_count($session); $ret.="\t\textref count: $q\n" if $q; $q=$api->session_handle_count($session); $ret.="\t\thandle count: $q\n" if $q; $q=join ',', $api->session_alias_list($session); $ret.="\t\tAliases: $q\n" if $q; } } $ret.="\n"; $poe_kernel->sig_handled; warn "$$: $ret"; return; } POE-Component-Server-HTTP-0.09/t/00_compile.t0000644000175000017500000000015310434666073020555 0ustar richardcrichardc#!perl -w use strict; use warnings; use Test::More tests => 1; require_ok('POE::Component::Server::HTTP'); POE-Component-Server-HTTP-0.09/t/first.gif0000644000175000017500000000103010434666073020252 0ustar richardcrichardcGIF89a2 6@c[y%Mw,2I8ͻ`(dihlp,tmx|pH,Ȥrl:ШtJZجvzxL.znp`"~4 Dy{H} } vss Γ Þ̃㯧sd%{k@IPSHq 7MdA.Tb1 PF$N@ ۴!rhʣ& rs$R@v(UzRA;f^,d@h6fQ5JЕ%S׵W <#s-P6_<ӨS^ͺװc˞M۸sͻ Nȓ+_μУKN]E;POE-Component-Server-HTTP-0.09/t/20_stream.t0000644000175000017500000002747110434666073020436 0ustar richardcrichardc#!/usr/bin/perl -w use strict; use Test::More tests => 13; use LWP::UserAgent; use HTTP::Request; use POE::Kernel; use POE::Component::Server::HTTP; use YAML; my $PORT = 2081; my $pid = fork; die "Unable to fork: $!" unless defined $pid; END { if ($pid) { kill 2, $pid or warn "Unable to kill $pid: $!"; } } $|++; #################################################################### if ($pid) { # we are parent # stop kernel from griping ${$poe_kernel->[POE::Kernel::KR_RUN]} |= POE::Kernel::KR_RUN_CALLED; print STDERR "$$: Sleep 2..."; sleep 2; print STDERR "continue\n"; if(@ARGV) { print STDERR "Please connect to http://localhost:$PORT/ with your browser and make sure everything works\n"; local @ARGV=(); {} while <>; } my $UA = LWP::UserAgent->new; ##################################### welcome my $req=HTTP::Request->new(GET => "http://localhost:$PORT/"); my $resp=$UA->request($req); ok(($resp->is_success and $resp->content_type eq 'text/html'), "got index") or die "resp=", Dump $resp; my $content = $resp->content; ok(($content =~ /multipart.txt/), "proper index") or die "resp=", Dump $content; ##################################### last.txt $req=HTTP::Request->new(GET => "http://localhost:$PORT/last.txt"); $resp=$UA->request($req); ok(($resp->is_success and $resp->content_type eq 'text/plain'), "got last.txt") or die "resp=", Dump $resp; $content = $resp->content; ok(($content =~ /everything worked/), "everything worked") or die "resp=", Dump $content; ##################################### multipart.txt $req=HTTP::Request->new(GET => "http://localhost:$PORT/multipart.txt"); $resp=$UA->request($req); ok(($resp->is_success and $resp->content_type =~ m(^multipart/mixed)), "got multipart.txt") or die "resp=", Dump $resp; $content = $resp->content; ok(($content =~ /everything worked/), "everything worked") or die "resp=", Dump $content; ##################################### last.gif my $last = File::Basename::dirname($0).'/last.gif'; open LAST, $last or die "Unable to open $last: $!"; { local $/; $last = ; } close LAST; ##################################### last.gif $req=HTTP::Request->new(GET => "http://localhost:$PORT/last.gif"); $resp=$UA->request($req); ok(($resp->is_success and $resp->content_type eq 'image/gif'), "got last.gif") or die "resp=", Dump $resp; $content = $resp->content; ok(($content eq $last), "everything worked"); ##################################### multipart.gif $req=HTTP::Request->new(GET => "http://localhost:$PORT/multipart.gif"); $resp=$UA->request($req); ok(($resp->is_success and $resp->content_type =~ m(^multipart/mixed)), "got multipart.txt") or die "resp=", Dump $resp; $content = $resp->content; $last = quotemeta $last; ok(($content =~ /$last/), "everything worked"); ##################################### multipart.mixed $req=HTTP::Request->new(GET => "http://localhost:$PORT/multipart.mixed"); $resp=$UA->request($req); ok(($resp->is_success and $resp->content_type =~ m(^multipart/mixed)), "got multipart.mixed") or die "resp=", Dump $resp; $content = $resp->content; ok(($content =~ /Please wait/), "first part worked"); ok(($content =~ /$last/), "last part worked"); } #################################################################### else { # we are the child Worker->spawn(port => $PORT); $poe_kernel->run(); } ########################################################### package Worker; use HTTP::Status; use POE::Kernel; use POE::Component::Server::HTTP; use POE; use File::Basename; sub DEBUG () { 0 } sub spawn { my($package, %parms)=@_; my $self = bless { dir => dirname($0), delay => 2, stream_todo => []}, $package; POE::Component::Server::HTTP->new( Port => $parms{port}, ContentHandler => { '/' => sub { $self->welcome(@_) }, '/favicon.ico' => sub { $self->favicon(@_) }, '/multipart.gif' => sub { $self->multipart(@_) }, '/multipart.mixed' => sub { $self->multipart_mixed(@_) }, '/last.gif' => sub { $self->last(@_) }, '/multipart.txt' => sub { $self->multipart_txt(@_) }, '/last.txt' => sub { $self->last_txt(@_) }, }, StreamHandler => sub { $self->stream_start(@_) } ); POE::Session->create( inline_states => { _start => sub { $self->_start() }, _stop => sub { DEBUG and warn "_stop\n" }, wait_start => sub { $self->wait_start(@_[ARG0..$#_])}, wait_done => sub { $self->wait_done(@_[ARG0..$#_])} } ); DEBUG and warn "Listening on port $parms{port}\n"; } ####################################### # POE event sub _start { my($self)=@_; $self->{session} = $poe_kernel->get_active_session->ID; $poe_kernel->alias_set(ref $self); return; } ####################################### # Called as ContentHandler sub welcome { my($self, $request, $response)=@_; DEBUG and warn "Welcome\n"; $response->code(RC_OK); $response->content_type('text/html; charset=iso-8859-1'); $response->content(< Hello world

Hello world from POE::Component::Server::HTTP

HTML return RC_OK; } ####################################### # Called as ContentHandler sub favicon { my($self, $request, $response)=@_; DEBUG and warn "favicon\n"; $response->code(RC_NOT_FOUND); $response->content_type('text/html; charset=iso-8859-1'); $response->content(< Go away

Go away

HTML return RC_NOT_FOUND; } ####################################### # Called as ContentHandler sub multipart { my($self, $request, $response)=@_; DEBUG and warn "multipart\n"; # Send an HTTP header and turn streaming on $self->multipart_start($request, $response); # After the HTTP header is sent, our StreamHandler will be called # Save the values that stream_start needs to do its work push @{$self->{stream_todo}}, [$request, $response, 'first.gif', 'last.gif']; return RC_OK; } ####################################### # Called as ContentHandler sub multipart_mixed { my($self, $request, $response)=@_; DEBUG and warn "multipart\n"; $self->multipart_start($request, $response); push @{$self->{stream_todo}}, [$request, $response, 'first.txt', 'last.gif']; return RC_OK; } ####################################### # Called as ContentHandler sub last { my($self, $request, $response)=@_; DEBUG and warn "last\n"; $response->code(RC_OK); $response->content_type('image/gif'); $response->content($self->data('last.gif')); return RC_OK; } ####################################### # Called as ContentHandler sub multipart_txt { my($self, $request, $response)=@_; DEBUG and warn "multipart_txt\n"; $self->multipart_start($request, $response); push @{$self->{stream_todo}}, [$request, $response, 'first.txt', 'last.txt']; return RC_OK; } ####################################### # Called as ContentHandler sub last_txt { my($self, $request, $response)=@_; DEBUG and warn "last_txt\n"; $response->code(RC_OK); $response->content_type('text/plain'); $response->content($self->data('last.txt')); return RC_OK; } ####################################### # Called as StreamHandler sub stream_start { my($self, $request, $response)=@_; DEBUG and warn "stream_start\n"; foreach my $todo (@{$self->{stream_todo}}) { my($request, $response, $first, $last)=@$todo; DEBUG and warn("post to wait_start for $first, $last\n"); $self->multipart_send($response, $first); # get into our POE session $poe_kernel->post($self->{session} => 'wait_start', $request, $response, $last); } $self->{stream_todo}=[]; return; } ####################################### # POE event sub wait_start { my($self, $request, $response, $next)=@_; DEBUG and warn "Going to wait for $self->{delay} seconds\n"; $poe_kernel->delay_set(wait_done => $self->{delay}, $request, $response, $next); return; } ####################################### # POE event sub wait_done { my($self, $request, $response, $next)=@_; DEBUG and warn "Waiting done, sending $next\n"; $self->multipart_send($response, $next); $self->multipart_end($request, $response); return; } ####################################### # Healper sub data { my($self, $name)=@_; my $file = "$self->{dir}/$name"; open FILE, $file or die "Can't open $file: $!"; { local $/; $file = ; } close FILE; return $file; } #################################################################### ####################################### # This function sends a file over the connection # We create a new HTTP response, with content and content_length # Because HTTP response->as_string sends HTTP status line, we hide it # behind a X-HTTP-Status header, just after the boundary. # This means that this part of the response looks like: # # --BoundaryString # X-HTTP-Status: HTTP/1.0 200 (OK) # Content-Type: text/plain # Content-Length: 13 # # Content here # # Setting Content-Length is important for images sub multipart_send { my($self, $response, $file)=@_; DEBUG and warn "multipart_send $file\n"; my $ct = 'image/gif'; $ct = 'text/plain' if $file =~ /txt$/; my $resp = $self->multipart_response($ct); my $data=$self->data($file); $resp->content($data); $resp->content_length(length($data)); $response->send("--$self->{boundary}\cM\cJX-HTTP-Status: "); $response->send($resp->as_string); return; } ####################################### # Create a HTTP::Response object to be sent as a part of the response sub multipart_response { my($self, $ct, $resp)=@_; $resp ||= HTTP::Response->new; $resp->content_type($ct||'text/plain'); $resp->code(200); return $resp; } ####################################### # Send an HTTP header that sets up multipart/mixed response # Also turns on streaming. # # PoCo::Server::HTTP will send the $response object, then run PostHandler # then switch to Streaming mode. sub multipart_start { my($self, $request, $response)=@_; $response->code(RC_OK); $self->{boundary} ||= 'ThisRandomString'; $response->content_type("multipart/mixed;boundary=$self->{boundary}"); $response->streaming(1); } ####################################### # The request is done. Turn off streaming and end the multipart response # Setting the header Connection to 'close' forces PoCo::Server::HTTP to # close the socket. This is needed so that the browsers stop "twirling". sub multipart_end { my($self, $request, $response)=@_; DEBUG and warn "Closing connection\n"; $response->close; $request->header(Connection => 'close'); $response->send("--$self->{boundary}--\cM\cJ"); } POE-Component-Server-HTTP-0.09/t/10_run.t0000644000175000017500000000630610434671423017733 0ustar richardcrichardc#!/usr/bin/perl -w use strict; use Test::More tests => 6 * 2; use LWP::UserAgent; use LWP::ConnCache; use HTTP::Request; use POE::Kernel; use POE::Component::Server::HTTP; use YAML; my $PORT = 2080; my $pid = fork; die "Unable to fork: $!" unless defined $pid; END { if ($pid) { kill 2, $pid or warn "Unable to kill $pid: $!"; } } #################################################################### if ($pid) { # we are parent # stop kernel from griping ${$poe_kernel->[POE::Kernel::KR_RUN]} |= POE::Kernel::KR_RUN_CALLED; print STDERR "$$: Sleep 2..."; sleep 2; print STDERR "continue\n"; my $UA = LWP::UserAgent->new; again: my $req=HTTP::Request->new(GET => "http://localhost:$PORT/"); my $resp=$UA->request($req); ok($resp->is_success, "got index") or die "resp=", Dump $resp; my $content=$resp->content; ok($content =~ /this is top/, "got top index"); $req=HTTP::Request->new(GET => "http://localhost:$PORT/honk/something.html"); $resp=$UA->request($req); ok($resp->is_success, "got something"); $content=$resp->content; ok($content =~ /this is honk/, "something honked"); $req=HTTP::Request->new(GET => "http://localhost:$PORT/bonk/zip.html"); $resp=$UA->request($req); ok(($resp->is_success and $resp->content_type eq 'text/html'), "get text/html"); $content=$resp->content; ok($content =~ /my friend/, 'my friend'); unless ($UA->conn_cache) { diag( "Enabling Keep-Alive and going again" ); $UA->conn_cache( LWP::ConnCache->new() ); goto again; } } #################################################################### else { # we are the child my $aliases = POE::Component::Server::HTTP->new( Port => $PORT, Address=>'localhost', MapOrder=>'bottom-first', ContentHandler => { '/' => \&top, '/honk/' => \&honk, '/bonk/' => \&bonk, '/bonk/zip.html' => \&bonk2, # '/shutdown.html' => \&shutdown }, # ErrorHandler => { '/' => \&error }, Headers => { Server => 'TestServer' }, ); $poe_kernel->run; } ####################################### sub top { my ($request, $response) = @_; $response->code(RC_OK); $response->content_type('text/plain'); $response->content("this is top"); return RC_OK; } ####################################### sub honk { my ($request, $response) = @_; $response->code(RC_OK); $response->content_type('text/plain'); $response->content("this is honk"); return RC_OK; } ####################################### sub bonk { my ($request, $response) = @_; $response->code(RC_OK); $response->content_type('text/plain'); $response->content("this is bonk"); return RC_OK; } ####################################### sub bonk2 { my ($request, $response) = @_; $response->code(RC_OK); $response->content_type('text/html'); $response->content(<<' HTML'); YEAH!

This, my friend, is the page you've been looking for.

HTML return RC_OK; } POE-Component-Server-HTTP-0.09/Changes0000644000175000017500000000427710434672057017503 0ustar richardcrichardc0.09 Tuesday 23rd May, 2006 Fixed for newer versions of POE where POE::Filter::HTTPD has changed representation from hashref to arrayref. 0.08 Friday 2nd September, 2005 Added documentation and tests for streaming mode from Philip Gwyn. Fixes for rt.cpan.org bugs #6747, #11349 Fixed t/30_error.t's call to Test::Builder. 0.07 Tuesday 22nd March, 2005 Make the test for 'close' in the Connection: close case insensitive (iPhoto 5 fixup from Dieter Mücke) 0.06 Sunday 2nd January, 2005 Added status messages to t/10_run.t Added default ErrorHandler, that prints a simple message to STDERR Improved detection of keepalive close Renamed t/compile.t to t/00_compile.t Added t/10_run.t Added t/30_error.t Added support for ErrorHandler Added DEBUG messages to HTTP.pm Added ->is_error to response and request objects Renamed Handler to Queue to minimize confusion in code. Cleaned up sub execute {} where $state was being set at funny places Reorganized Map, for ErrorHandler and so that filenames don't get a trailing / ->create() now returns a hashref, keys are httpd or tcp, values are the session aliases of the respective sessions. All work except test fixups by Philip Gwyn 0.05 Tuesday 27th July, 2004 Rejigged things into the new-fangled lib/*, t/*.t arrangement (richardc) The Fotango opensource repository is now subversion: http://opensource.fotango.com/svn/trunk/POE-Component-Server-HTTP/ HTTP/1.1 keepalive behaviour 0.04 Thu Oct 9 13:43:46 BST 2003 Removed old _signal handler Specified correct dependencies in makefile Imported into Fotango opensource CVS at CVSROOT=:pserver:anonymous@she-ra.fotango.com:/var/repository 0.03 Thu Nov 14 07:01:42 CET 2002 Applied patch https://rt.cpan.org/NoAuth/Bug.html?id=1609 from Eric Calder to add a new shutdown event. Added signature file 0.02 2002-03-17 Incorpated long overdue patch from Matt Sargeant to support 5.005 Removed "use CGI::Cookie" since it wasn't used in the code. Changed typo to avoid warning (From Eric Calder). Made the installation work as intended with the subclasses 0.01 Wed Jan 16 21:57:03 2002 First release POE-Component-Server-HTTP-0.09/MANIFEST0000644000175000017500000000051010434666073017324 0ustar richardcrichardcChanges Makefile.PL MANIFEST MANIFEST.SKIP META.yml README test.perl lib/POE/Component/Server/HTTP.pm lib/POE/Component/Server/HTTP/Connection.pm lib/POE/Component/Server/HTTP/Request.pm lib/POE/Component/Server/HTTP/Response.pm t/00_compile.t t/10_run.t t/20_stream.t t/30_error.t t/first.gif t/first.txt t/last.gif t/last.txt POE-Component-Server-HTTP-0.09/lib/0002755000175000017500000000000010434672213016740 5ustar richardcrichardcPOE-Component-Server-HTTP-0.09/lib/POE/0002755000175000017500000000000010434672213017363 5ustar richardcrichardcPOE-Component-Server-HTTP-0.09/lib/POE/Component/0002755000175000017500000000000010434672213021325 5ustar richardcrichardcPOE-Component-Server-HTTP-0.09/lib/POE/Component/Server/0002755000175000017500000000000010434672213022573 5ustar richardcrichardcPOE-Component-Server-HTTP-0.09/lib/POE/Component/Server/HTTP/0002755000175000017500000000000010434672213023352 5ustar richardcrichardcPOE-Component-Server-HTTP-0.09/lib/POE/Component/Server/HTTP/Response.pm0000644000175000017500000000165010434666073025515 0ustar richardcrichardcuse strict; package POE::Component::Server::HTTP::Response; use vars qw(@ISA); use HTTP::Response; @ISA = qw(HTTP::Response); use POE; sub streaming { my $self = shift; if (@_) { if ($_[0]) { $self->{streaming} = 1; } else { $self->{streaming} = 0; } } return $self->{streaming}; } sub is_error { my $self = shift; if (@_) { if ($_[0]) { $self->{is_error} = 1; } else { $self->{is_error} = 0; } } return $self->{is_error}; } sub send { my $self = shift; $self->{connection}->{wheel}->put(@_); } sub continue { my $self = shift; $poe_kernel->post($self->{connection}->{session}, 'execute' => $self->{connection}->{my_id}); } sub close { my $self = shift; $self->{streaming} = 0; shift @{$self->{connection}->{handlers}->{Queue}}; } 1; POE-Component-Server-HTTP-0.09/lib/POE/Component/Server/HTTP/Request.pm0000644000175000017500000000102210434666073025340 0ustar richardcrichardcuse strict; package POE::Component::Server::HTTP::Request; use vars qw(@ISA); use HTTP::Request; @ISA = qw(HTTP::Request); sub new { my $package = shift; my $self = $package->SUPER::new(@_); $self->is_error($self->method eq 'ERROR'); return $self } sub connection { return $_[0]->{connection}; } sub is_error { my $self = shift; if (@_) { if($_[0]) { $self->{is_error} = 1; } else { $self->{is_error} = 0; } } return $self->{is_error}; } 1; POE-Component-Server-HTTP-0.09/lib/POE/Component/Server/HTTP/Connection.pm0000644000175000017500000000157510434666073026024 0ustar richardcrichardcuse strict; package POE::Component::Server::HTTP::Connection; sub new { return bless {}; } sub remote_host { return "N/A"; } sub remote_ip { my $self = shift; return $self->{remote_ip}; } sub local_addr { my $self = shift; return $self->{local_addr}; } sub remote_addr { my $self = shift; return $self->{remote_addr}; } sub remote_logname { my $self = shift; return "N/A"; } sub user { my $self = shift; if (@_) { $self->{user} = shift; } return $self->{user}; } sub authtype { my $self = shift; return $self->{authtype}; } sub aborted { return 0; } sub fileno { return 0; } sub clone { my $self = shift; my $new = bless { %$self }; return $new; } sub response { my $self = shift; return $self->{response}; } sub request { my $self = shift; return $self->{request}; } 1; POE-Component-Server-HTTP-0.09/lib/POE/Component/Server/HTTP.pm0000644000175000017500000005270710434672123023721 0ustar richardcrichardcpackage POE::Component::Server::HTTP; use strict; use Socket qw(inet_ntoa); use HTTP::Date; use HTTP::Status; use File::Spec; use Exporter (); use vars qw(@ISA @EXPORT $VERSION); @ISA = qw(Exporter); use constant RC_WAIT => -1; use constant RC_DENY => -2; @EXPORT = qw(RC_OK RC_WAIT RC_DENY); use POE qw(Wheel::ReadWrite Driver::SysRW Session Filter::Stream Filter::HTTPD); use POE::Component::Server::TCP; use Sys::Hostname qw(hostname); $VERSION = "0.09"; use POE::Component::Server::HTTP::Response; use POE::Component::Server::HTTP::Request; use POE::Component::Server::HTTP::Connection; use constant DEBUG => 0; use Carp; my %default_headers = ( "Server" => "POE HTTPD Component/$VERSION ($])", ); sub new { my $class = shift; my $self = bless {@_}, $class; $self->{Headers} = { %default_headers, ($self->{Headers} ? %{$self->{Headers}}: ())}; $self->{TransHandler} = [] unless($self->{TransHandler}); $self->{ErrorHandler} = { '/' => \&default_http_error, } unless($self->{ErrorHandler}); $self->{PreHandler} = {} unless($self->{PreHandler}); $self->{PostHandler} = {} unless($self->{PostHandler}); if (ref($self->{ContentHandler}) ne 'HASH') { croak "You need a default content handler or a ContentHandler setup" unless(ref($self->{DefaultContentHandler}) eq 'CODE'); $self->{ContentHandler} = {}; $self->{ContentHandler}->{'/'} = $self->{DefaultContentHandler}; } if (ref $self->{ErrorHandler} ne 'HASH') { croak "ErrorHandler must be a hashref or a coderef" unless(ref($self->{ErrorHandler}) eq 'CODE'); $self->{ErrorHandler}={'/' => $self->{ErrorHandler}}; } # DWIM on these handlers foreach my $phase (qw(PreHandler PostHandler)) { # NOTE: we want the following 2 cases to fall through to the last case if('CODE' eq ref $self->{$phase}) { # CODE to { / => [ CODE ]} $self->{$phase}={'/' => [$self->{$phase}]}; } if('ARRAY' eq ref $self->{$phase}) { # ARRAY to { / => ARRAY } $self->{$phase}={'/' => $self->{$phase}}; } if('HASH' eq ref $self->{$phase}) { # check all hash keys while(my($path, $todo)=each %{$self->{$phase}}) { if('CODE' eq ref $todo) { $self->{$phase}{$path}=[$todo]; next; } next if 'ARRAY' eq ref $todo; croak "$phase\->{$path} must be an arrayref"; } next; } croak "$phase must be a hashref"; } $self->{Hostname} = hostname() unless($self->{Hostname}); my $alias = "PoCo::Server::HTTP::[ID]"; my $tcp_alias = $alias . "::TCP"; my $session = POE::Session->create( inline_states => { _start => sub { my $id=$_[SESSION]->ID; $alias =~ s/\[ID\]/$id/; $tcp_alias =~ s/\[ID\]/$id/; $_[KERNEL]->alias_set($alias); }, _stop => sub { }, accept => \&accept, input => \&input, execute => \&execute, error => \&error, shutdown => sub { my ($kernel, $session, $heap) = @_[KERNEL, SESSION, HEAP]; $kernel->call($tcp_alias, "shutdown"); $kernel->alias_remove($alias); }, }, heap => { self => $self } ); POE::Component::Server::TCP->new( Port => $self->{Port}, Address => $self->{Address}, Alias => $tcp_alias, Error => sub { $poe_kernel->post($session, 'error', @_[ARG0..ARG2]); }, # ClientError => sub { # $poe_kernel->post($session, 'error', @_[ARG0..ARG2]); # }, Acceptor => sub { $poe_kernel->post($session,'accept',@_[ARG0..ARG2]); }); return { httpd => $alias, tcp => $tcp_alias }; } sub handler_queue { return [qw( TransHandler Map PreHandler ContentHandler Send PostHandler Cleanup )]; } sub error_queue { return [qw( Map ErrorHandler PostHandler Cleanup )]; } # Set up queue for handling this request sub rebuild_queue { my( $self, $handlers) = @_; my $now = $handlers->{Queue}[0]; # what phase are we about to do? if (not $now) { # this means we are post Cleanup # (which could be keep-alive) DEBUG and warn "Error post-Cleanup!"; # we need Map to turn set up ErrorHandler $handlers->{Queue} = ['Map', 'ErrorHandler', 'Cleanup']; # Note : sub error set up fake request/response objects, etc } elsif ($now eq 'TransHandler' or $now eq 'Map' or $now eq 'PreHandler' or $now eq 'ContentHandler' or $now eq 'Send' or $now eq 'PostHandler') { $handlers->{Queue}=$self->error_queue; } elsif ($now eq 'Cleanup') { # we need Map to turn set up ErrorHandler unshift @{$handlers->{Queue}}, 'Map', 'ErrorHandler'; } # clear these lists, so that Map builds new ones $handlers->{PostHandler} = []; $handlers->{PreHandler} = []; } sub accept { my ($socket,$remote_addr, $remote_port) = @_[ARG0, ARG1, ARG2]; my $self = $_[HEAP]->{self}; my $connection = POE::Component::Server::HTTP::Connection->new(); $connection->{remote_ip} = inet_ntoa($remote_addr); $connection->{remote_addr} = getpeername($socket); $connection->{local_addr} = getsockname($socket); $connection->{handlers} = { TransHandler => [@{$self->{TransHandler}}], PreHandler => [], ContentHandler => undef, PostHandler => [], # IMHO, Queue should be set in 'input' --PG Queue => $self->handler_queue, }; my $wheel = POE::Wheel::ReadWrite->new( Handle => $socket, Driver => POE::Driver::SysRW->new, Filter => POE::Filter::HTTPD->new(), InputEvent => 'input', FlushedEvent => 'execute', ErrorEvent => 'error' ); DEBUG and warn "Accept remote_ip=$connection->{remote_ip} id=", $wheel->ID; $_[HEAP]->{wheels}->{$wheel->ID} = $wheel; $_[HEAP]->{c}->{$wheel->ID} = $connection } sub input { my ($request,$id) = @_[ARG0, ARG1]; DEBUG and warn "Input id=$id uri=", $request->uri->as_string; bless $request, 'POE::Component::Server::HTTP::Request'; my $c = $_[HEAP]->{c}->{$id}; my $self = $_[HEAP]->{self}; if ($request->uri) { $request->uri->scheme('http'); $request->uri->host($self->{Hostname}); $request->uri->port($self->{Port}); } $request->{connection} = $c; my $response = POE::Component::Server::HTTP::Response->new(); $response->{connection} = $c; $c->{wheel} = $_[HEAP]->{wheels}->{$id}; $c->{request} = $request; $c->{response} = $response; $c->{session} = $_[SESSION]; $c->{my_id} = $id; $poe_kernel->yield('execute',$id); } sub error { my ($op, $errnum, $errstr, $id) = @_[ARG0..ARG3]; unless ( $_[HEAP]->{c}{$id} ) { warn "Error $op $errstr ($errnum) happened after Cleanup!\n"; return; } my $c = $_[HEAP]->{c}->{$id}; my $self = $_[HEAP]->{self}; DEBUG and warn "$$: HTTP error op=$op errnum=$errnum errstr=$errstr id=$id\n"; if ($op eq 'accept') { die "$$: HTTP error op=$op errnum=$errnum errstr=$errstr id=$id\n"; } elsif ($op eq 'read' or $op eq 'write') { # connection closed or other error ## Create some temporary objects if needed unless($c->{request}) { my $request = POE::Component::Server::HTTP::Request->new( ERROR => '/' ); $request->{connection} = $c; $c->{request}=$request; } $c->{request}->header(Operation => $op); $c->{request}->header(Errnum => $errnum); $c->{request}->header(Error => $errstr); unless ($c->{response}) { my $response = POE::Component::Server::HTTP::Response->new(); $response->{connection} = $c; $c->{response}=$response; } $c->{session} ||= $_[SESSION]; $c->{my_id} ||= $id; $c->{wheel} ||= $_[HEAP]{wheels}{$id}; # mark everything hence forth as an error $c->{request}->is_error(1); $c->{response}->is_error(1); # and rebuild the queue $self->rebuild_queue($c->{handlers}); $poe_kernel->yield('execute',$id); } } sub default_http_error { my ($request, $response) = @_; my $op = $request->header('Operation'); my $errstr = $request->header('Error'); my $errnum = $request->header('Errnum'); return if $errnum == 0 and $op eq 'read'; # socket closed warn "Error during HTTP $op: $errstr ($errnum)\n"; } sub execute { my $id = $_[ARG0]; my $self = $_[HEAP]->{self}; my $connection = $_[HEAP]->{c}->{$id}; my $handlers = $connection->{handlers}; my $response = $connection->{response}; my $request = $connection->{request}; my $state; HANDLERS: while (1) { $state = $handlers->{Queue}->[0]; DEBUG and warn "Execute state=$state id=$id"; if ($state eq 'Map') { $self->state_Map( $request->uri ? $request->uri->path : '', $handlers, $request ); shift @{$handlers->{Queue}}; next; } elsif ($state eq 'Send') { $self->state_Send( $response, $_[HEAP]->{wheels}->{$id} ); shift @{$handlers->{Queue}}; last; } elsif ($state eq 'ContentHandler' or $state eq 'ErrorHandler') { # this empty sub should really make a 404 my $sub = $handlers->{ $state } || sub {}; # XXX: we should wrap this in an eval and return 500 my $retvalue = $sub->($request, $response); shift @{$handlers->{Queue}}; if ($retvalue == RC_WAIT) { if( $state eq 'ErrorHandler') { warn "ErrorHandler is not allowed to return RC_WAIT"; } else { last HANDLERS; } } next; } elsif ($state eq 'Cleanup') { if (not $response->is_error and $response->streaming()) { $_[HEAP]->{wheels}->{$id}->set_output_filter(POE::Filter::Stream->new() ); unshift(@{$handlers->{Queue}},'Streaming'); next HANDLERS; } delete($response->{connection}); delete($request->{connection}); # under HTTP/1.1 connections are always kept alive, unless # there's a Connection: close present my $close = 1; if ( $request->protocol eq 'HTTP/1.1' ) { $close = 0; # keepalive # It turns out the connection field can contain multiple # comma separated values my $conn = $request->header('Connection'); $close = 1 if qq(,$conn,) =~ /,\s*close\s*,/i; } unless ($close) { DEBUG and warn "Keepalive connection still active"; # Breaking encapsulation causes immolation --richardc # We'll need a new POE::Filter::HTTPD $_[HEAP]{wheels}{$id}[2] = (ref $_[HEAP]{wheels}{$id}[2])->new; # IMHO, Queue should be set in 'input' --PG $handlers->{Queue} = $self->handler_queue; } else { DEBUG and warn "Close connection"; delete($connection->{handlers}); delete($connection->{wheel}); delete($_[HEAP]->{c}->{$id}); delete($_[HEAP]->{wheels}->{$id}); } last HANDLERS; } elsif ($state eq 'Streaming') { $self->{StreamHandler}->($request, $response); last HANDLERS; } DISPATCH: # this is used for {Trans,Pre,Post}Handler while (1) { my $handler = shift(@{$handlers->{$state}}); last DISPATCH unless($handler); my $retvalue = $handler->($request,$response); if ($retvalue == RC_DENY) { last DISPATCH; } elsif ($retvalue == RC_WAIT) { last HANDLERS; } } shift @{$handlers->{Queue}}; last unless(0 != @{$handlers->{Queue}}); } } sub state_Map { my $self = shift; my $path = shift; my $handlers = shift; my $request = shift; my $filename; (undef, $path,$filename) = File::Spec->splitpath($path); my @dirs = File::Spec->splitdir($path); pop @dirs; DEBUG and warn "dirs=", join ',', @dirs; my @check; my $fullpath; foreach my $dir (@dirs) { $fullpath .= $dir.'/'; push @check, $fullpath; } push(@check, "$check[-1]$filename") if($filename); DEBUG and warn "check=", join ',', @check; my @todo; unless ($request->is_error) { @todo=qw(PreHandler ContentHandler PostHandler); } else { @todo=qw(ErrorHandler PostHandler); } foreach my $path (@check) { foreach my $phase (@todo) { next unless exists($self->{$phase}->{$path}); if ('ARRAY' eq ref $self->{$phase}{$path}) { push @{$handlers->{$phase}}, @{$self->{$phase}->{$path}}; } else { $handlers->{$phase}=$self->{$phase}->{$path}; } } } require Data::Dumper if DEBUG; DEBUG and warn "Map ", Data::Dumper::Dumper( $handlers ); } sub state_Send { my $self = shift; my $response = shift; my $wheel = shift; $response->header(%{$self->{Headers}}); unless ($response->header('Date')) { $response->header('Date',time2str(time)); } if (!($response->header('Content-Lenth')) && !($response->streaming())) { use bytes; $response->header('Content-Length',length($response->content)); } $wheel->put($response); } 1; __END__ =head1 NAME POE::Component::Server::HTTP - Foundation of a POE HTTP Daemon =head1 SYNOPSIS use POE::Component::Server::HTTP; use HTTP::Status; my $aliases = POE::Component::Server::HTTP->new( Port => 8000, ContentHandler => { '/' => \&handler1, '/dir/' => sub { ... }, '/file' => sub { ... } }, Headers => { Server => 'My Server' }, ); sub handler { my ($request, $response) = @_; $response->code(RC_OK); $response->content("Hi, you fetched ". $request->uri); return RC_OK; } POE::Kernel->call($aliases->{httpd}, "shutdown"); # next line isn't really needed POE::Kernel->call($aliases->{tcp}, "shutdown"); =head1 DESCRIPTION POE::Component::Server::HTTP (PoCo::HTTPD) is a framework for building custom HTTP servers based on POE. It is loosely modeled on the ideas of apache and the mod_perl/Apache module. It is built alot on work done by Gisle Aas on HTTP::* modules and the URI module which are subclassed. PoCo::HTTPD lets you register different handler, stacked by directory that will be run during the cause of the request. =head2 Handlers Handlers are put on a stack in fifo order. The path /foo/bar/baz/honk.txt will first push the handlers of / then of /foo/ then of /foo/bar/, then of /foo/bar/baz/, and lastly /foo/bar/baz/honk.txt. Pay attention to directories! A request for /honk will not match /honk/ as you are used to with apache. If you want /honk to act like a directory, you should have a handler for /honk which redirects to /honk/. However, there can be only one ContentHandler and if any handler installs a ContentHandler that will override the old ContentHandler. If no handler installs a ContentHandler it will find the closest one directory wise and use it. There is also a special StreamHandler which is a coderef that gets invoked if you have turned on streaming by doing $response->streaming(1); Handlers take the $request and $response objects as arguments. =over 4 =item RC_OK Everything is ok, please continue processing. =item RC_DENY If it is a TransHandler, stop translation handling and carry on with a PreHandler, if it is a PostHandler do nothing, else return denied to the client. =item RC_WAIT This is a special handler that suspends the execution of the handlers. They will be suspended until $response->continue() is called, this is usefull if you want to do a long request and not blocck. =back The following handlers are available. =over 4 =item TransHandler TransHandlers are run before the URI has been resolved, giving them a chance to change the URI. They can therefore not be registred per directory. new(TransHandler => [ sub {return RC_OK} ]); A TransHandler can stop the dispatching of TransHandlers and jump to the next handler type by specifing RC_DENY; =item PreHandler PreHandlers are stacked by directory and run after TransHandler but before the ContentHandler. They can change ContentHandler (but beware, other PreHandlers might also change it) and push on PostHandlers. new(PreHandler => { '/' => [sub {}], '/foo/' => [\&foo]}); =item ContentHandler The handler that is supposed to give the content. When this handler returns it will send the response object to the client. It will automaticly add Content-Length and Date if these are not set. If the response is streaming it will make sure the correct headers are set. It will also expand any cookies which have been pushed onto the response object. new(ContentHandler => { '/' => sub {}, '/foo/' => \&foo}); =item ErrorHandler This handler is called when there is a read or write error on the socket. This is most likely caused by the remote side closing the connection. $resquest->is_error and $response->is_error will return true. Note that C will still called, but C and C won't be. It is a map to coderefs just like ContentHandler is. =item PostHandler These handlers are run after the socket has been flushed. new(PostHandler => { '/' => [sub {}], '/foo/' => [\&foo]}); =item StreamHandler If you turn on streaming in any other handler, the request is placed in streaming mode. This handler is called, with the usual parameters, when streaming mode is first entered, and subsequently when each block of data is flushed to the client. Streaming mode is turned on via the C<$response> object: $response->streaming(1); You deactivate streaming mode with the same object: $response->close; Content is also sent to the client via the C<$response> object: $response->send($somedata); The output filter is set to POE::Filter::Stream, which passes the data through unchanged. If you are doing a multipart/mixed response, you will have to set up your own headers. Example: sub new { ..... POE::Component::Filter::HTTP->new( ContentHandler => { '/someurl' => sub { $self->someurl(@_) }, StreamHandler => sub { $self->stream(@_), ); } sub someurl { my($self, $resquest, $response)=@_; $self->{todo} = [ .... ]; $response->streaming(1); $response->code(RC_OK); # you must set up your response header $response->content_type(...); return RC_OK; } sub stream { my($self, $resquest, $response)=@_; if( @{$self->{todo}} ) { $response->send(shift @{$self->{todo}}); } else { $response->close; } } Another example can be found in t/30_stream.t. The parts dealing with multipart/mixed are well documented and at the end of the file. NOTE: Changes in streaming mode are only verified when StreamHandler exits. So you must either turn streaming off in your StreamHandler, or make sure that the StreamHandler will be called again. This last is done by sending data to the client. If for some reason you have no data to send, you can get the same result with C. Remember that this will also cause the StreamHandler to be called one more time. my $aliases=POE::Component::Filter::HTTP->new( ....); # and then, when the end of the stream in met $response->close; $response->continue; NOTE: even when the stream ends, the client connection will be held open if Keepalive is active. To force the connection closed, set the I header to I: $resquest->header(Connection => 'close'); I =back =head1 Events The C event may be sent to the component indicating that it should shut down. The event may be sent using the return value of the I method (which is a session id) by either post()ing or call()ing. I've experienced some problems with the session not receiving the event when it gets post()ed so call() is advised. =head1 See Also Please also take a look at L, L, L, L and L =head1 TODO =over 4 =item Document Connection Response and Request objects. =item Write more tests =item Add a PoCo::Server::HTTP::Session that matches a http session against poe session using cookies or other state system =item Add more options to streaming =item Figure out why post()ed C events don't get received. =item Probably lots of other API changes =back =head1 AUTHOR Arthur Bergman, arthur@contiller.se Additional hacking by Philip Gwyn, poe-at-pied.nu Released under the same terms as POE. =cut POE-Component-Server-HTTP-0.09/MANIFEST.SKIP0000644000175000017500000000004610434666073020075 0ustar richardcrichardc.svn ^Makefile$ ~$ ^blib ^pm_to_blib$ POE-Component-Server-HTTP-0.09/META.yml0000644000175000017500000000106110434672213017437 0ustar richardcrichardc# http://module-build.sourceforge.net/META-spec.html #XXXXXXX This is a prototype!!! It will change in the future!!! XXXXX# name: POE-Component-Server-HTTP version: 0.09 version_from: lib/POE/Component/Server/HTTP.pm installdirs: site requires: File::Spec: 0 HTTP::Date: 0 HTTP::Status: 0 POE: 0.3007 POE::API::Peek: 0 YAML: 0 distribution_type: module generated_by: ExtUtils::MakeMaker version 6.17 POE-Component-Server-HTTP-0.09/test.perl0000644000175000017500000000301610434666073020042 0ustar richardcrichardc use strict; use Data::Dumper; #sub POE::Kernel::TRACE_DEFAULT () { 1 } #sub POE::Kernel::ASSERT_DEFAULT () { 1 } #sub POE::Kernel::TRACE_DEFAULT () { 1 } BEGIN { require 'HTTP.pm'; POE::Component::Server::HTTP->import(); } #use HTTP::Status; use POE; #use Carp qw(confess); #$SIG{INT} = sub { confess }; POE::Component::Server::HTTP->new( Port => 8000, ContentHandler => { '/' => \&callback, '/arthur/' => \&artur, }, #TransHandler => [\&uri], #PostHandler => { # '/' => [\&post], #}, #StreamHandler => \&stream, Headers => { Foo => "bar" }); sub stream { my ($request, $response) = @_; $response->send(qq!!); $response->close(); } sub uri { # my ($request,$response) = @_; # $response->next(); # return RC_WAIT; } sub artur { my $request = shift; my $response = shift; my $connection = shift; $response->code('200'); $response->content("Welcome to " . $request->uri); return $response; } sub callback { my $request = shift; my $response = shift; my $connection = $request->connection; #my $cookie = CGI::Cookie->new(-name => "FOO", -value => "bar"); $response->code('200'); $response->push_header("Content-type", "text/html"); #$response->push_header('Set-Cookie' => $cookie->as_string); $response->content("Welcome ".$connection->remote_ip); # $response->streaming(1); return $response; } $poe_kernel->run(); POE-Component-Server-HTTP-0.09/README0000644000175000017500000000122110434666073017053 0ustar richardcrichardcPOE/Component/Server/HTTP ========================= This is a nearly stable release of PoCo::Server::HTTP It makes it easy to write HTTP servers in POE. The design is roughly based on the ideas of mod_perl and lets you add hooks and intercept in a numbber of different stages. Look at the test cases in t/ if you want help. In particular, t/20_stream.t contains a good example of multipart/mixed responses. INSTALLATION To install this module type the following: perl Makefile.PL make make test make install COPYRIGHT AND LICENCE Released under the same terms as POE Copyright (C) 2002 Arthur Bergman Copyright (C) 2005 Philip Gwyn POE-Component-Server-HTTP-0.09/Makefile.PL0000644000175000017500000000162510434666073020155 0ustar richardcrichardcuse ExtUtils::MakeMaker; eval { require POE::Component::Server::HTTP; if ($POE::Component::Server::HTTP::VERSION <= 0.05) { warn < 'POE::Component::Server::HTTP', AUTHOR => 'Arthur Bergman', VERSION_FROM => 'lib/POE/Component/Server/HTTP.pm', # finds $VERSION ABSTRACT_FROM => 'lib/POE/Component/Server/HTTP.pm', PREREQ_PM => { POE => '0.3007', # KR_RUN is certainly newer than 0.27, and # 0.3005 has a broken POE::Filter::HTTPD POE::API::Peek => 0, File::Spec => 0, HTTP::Date => 0, HTTP::Status => 0, YAML => 0, }, );