File Coverage

blib/lib/POE/Component/Server/SimpleHTTP.pm
Criterion Covered Total %
statement 34 36 94.4
branch n/a
condition n/a
subroutine 12 12 100.0
pod n/a
total 46 48 95.8


line stmt bran cond sub pod time code
1             package POE::Component::Server::SimpleHTTP;
2              
3 7     7   1577479 use strict;
  7         19  
  7         274  
4 7     7   1203 use warnings;
  7         13  
  7         257  
5              
6 7     7   33 use vars qw($VERSION);
  7         12  
  7         349  
7              
8             $VERSION = '2.18';
9              
10 7     7   35 use POE;
  7         16  
  7         79  
11 7     7   12982 use POE::Wheel::SocketFactory;
  7         73394  
  7         238  
12 7     7   6643 use POE::Wheel::ReadWrite;
  7         64593  
  7         223  
13 7     7   8946 use POE::Filter::HTTPD;
  7         319335  
  7         260  
14 7     7   6749 use POE::Filter::Stream;
  7         2645  
  7         234  
15              
16 7     7   50 use Carp qw( croak );
  7         15  
  7         437  
17 7     7   101 use Socket;
  7         14  
  7         5820  
18              
19 7     7   45 use HTTP::Date qw( time2str );
  7         15  
  7         312  
20              
21 7     7   4158 use POE::Component::Server::SimpleHTTP::Connection;
  0            
  0            
22             use POE::Component::Server::SimpleHTTP::Response;
23             use POE::Component::Server::SimpleHTTP::State;
24              
25             BEGIN {
26              
27             # Debug fun!
28             if ( !defined &DEBUG ) {
29             eval "sub DEBUG () { 0 }";
30             }
31              
32             # Our own definition of the max retries
33             if ( !defined &MAX_RETRIES ) {
34             eval "sub MAX_RETRIES () { 5 }";
35             }
36             }
37              
38             use MooseX::POE;
39             use Moose::Util::TypeConstraints;
40              
41              
42             has 'alias' => (
43             is => 'ro',
44             );
45              
46             has 'address' => (
47             is => 'ro',
48             );
49              
50             has 'port' => (
51             is => 'ro',
52             default => sub { 0 },
53             writer => '_set_port',
54             );
55              
56             has 'hostname' => (
57             is => 'ro',
58             default => sub { require Sys::Hostname; return Sys::Hostname::hostname(); },
59             );
60              
61             has 'proxymode' => (
62             is => 'ro',
63             isa => 'Bool',
64             default => sub { 0 },
65             );
66              
67             has 'keepalive' => (
68             is => 'ro',
69             isa => 'Bool',
70             default => sub { 0 },
71             );
72              
73             has 'sslkeycert' => (
74             is => 'ro',
75             isa => subtype 'ArrayRef' => where { scalar @$_ == 2 },
76             );
77              
78             has 'sslintermediatecacert' => (
79             is => 'ro',
80             isa => 'Str',
81             );
82              
83             has 'headers' => (
84             is => 'ro',
85             isa => 'HashRef',
86             default => sub {{}},
87             );
88              
89             has 'handlers' => (
90             is => 'ro',
91             isa => 'ArrayRef',
92             required => 1,
93             writer => '_set_handlers',
94             );
95              
96             has 'errorhandler' => (
97             is => 'ro',
98             isa => 'HashRef',
99             default => sub {{}},
100             );
101              
102             has 'loghandler' => (
103             is => 'ro',
104             isa => 'HashRef',
105             default => sub {{}},
106             );
107              
108             has 'log2handler' => (
109             is => 'ro',
110             isa => 'HashRef',
111             default => sub {{}},
112             );
113              
114             has 'setuphandler' => (
115             is => 'ro',
116             isa => 'HashRef',
117             default => sub {{}},
118             );
119              
120             has 'retries' => (
121             traits => ['Counter'],
122             is => 'ro',
123             isa => 'Num',
124             default => sub { 0 },
125             handles => {
126             inc_retry => 'inc',
127             dec_retry => 'dec',
128             reset_retries => 'reset',
129             },
130             );
131              
132             has '_requests' => (
133             is => 'ro',
134             isa => 'HashRef',
135             default => sub {{}},
136             init_arg => undef,
137             clearer => '_clear_requests',
138             );
139              
140             has '_connections' => (
141             is => 'ro',
142             isa => 'HashRef',
143             default => sub {{}},
144             init_arg => undef,
145             clearer => '_clear_connections',
146             );
147              
148             has '_chunkcount' => (
149             is => 'ro',
150             isa => 'HashRef',
151             default => sub {{}},
152             init_arg => undef,
153             clearer => '_clear_chunkcount',
154             );
155              
156             has '_responses' => (
157             is => 'ro',
158             isa => 'HashRef',
159             default => sub {{}},
160             init_arg => undef,
161             clearer => '_clear_responses',
162             );
163              
164             has '_factory' => (
165             is => 'ro',
166             isa => 'POE::Wheel::SocketFactory',
167             init_arg => undef,
168             clearer => '_clear_factory',
169             writer => '_set_factory',
170             );
171              
172             sub BUILDARGS {
173             my $class = shift;
174             my %args = @_;
175             $args{lc $_} = delete $args{$_} for keys %args;
176              
177             if ( $args{sslkeycert} and ref $args{sslkeycert} eq 'ARRAY'
178             and scalar @{ $args{sslkeycert} } == 2 ) {
179              
180             eval {
181             require POE::Component::SSLify;
182             import POE::Component::SSLify
183             qw( SSLify_Options SSLify_GetSocket Server_SSLify SSLify_GetCipher SSLify_GetCTX );
184             SSLify_Options( @{ $args{sslkeycert} } );
185             };
186             if ($@) {
187             warn "Unable to load PoCo::SSLify -> $@" if DEBUG;
188             delete $args{sslkeycert};
189             }
190             else {
191             if ( $args{sslintermediatecacert} ) {
192             my $ctx = SSLify_GetCTX();
193             Net::SSLeay::CTX_load_verify_locations($ctx, $args{sslintermediatecacert}, '');
194             }
195             }
196             }
197              
198             return $class->SUPER::BUILDARGS(%args);
199             }
200              
201             sub session_id {
202             shift->get_session_id;
203             }
204              
205             sub getsockname {
206             shift->_factory->getsockname;
207             }
208              
209             sub shutdown {
210             my $self = shift;
211             $poe_kernel->call( $self->get_session_id, 'SHUTDOWN', @_ );
212             }
213              
214             # This subroutine, when SimpleHTTP exits, will search for leaks
215             sub STOP {
216             my $self = $_[OBJECT];
217             # Loop through all of the requests
218             foreach my $req ( keys %{ $self->_requests } ) {
219              
220             # Bite the programmer!
221             warn 'Did not get DONE/CLOSE event for Wheel ID '
222             . $req
223             . ' from IP '
224             . $self->_requests->{$req}->response->connection->remote_ip;
225             }
226              
227             # All done!
228             return 1;
229             }
230              
231             sub START {
232             my ($kernel,$self) = @_[KERNEL,OBJECT];
233             $kernel->alias_set( $self->alias ) if $self->alias;
234             $kernel->refcount_increment( $self->get_session_id, __PACKAGE__ )
235             unless $self->alias;
236             MassageHandlers( $self->handlers );
237             # Start Listener
238             $kernel->yield( 'start_listener' );
239             return;
240             }
241              
242             # 'SHUTDOWN'
243             # Stops the server!
244             event 'SHUTDOWN' => sub {
245             my ($kernel,$self,$graceful) = @_[KERNEL,OBJECT,ARG0];
246             # Shutdown the SocketFactory wheel
247             $self->_clear_factory if $self->_factory;
248              
249             # Debug stuff
250             warn 'Stopped listening for new connections!' if DEBUG;
251              
252             # Are we gracefully shutting down or not?
253             if ( $graceful ) {
254              
255             # Check for number of requests
256             if ( keys( %{ $self->_requests } ) == 0 ) {
257              
258             # Alright, shutdown anyway
259              
260             # Delete our alias
261             $kernel->alias_remove( $_ ) for $kernel->alias_list();
262             $kernel->refcount_decrement( $self->get_session_id, __PACKAGE__ )
263             unless $self->alias;
264              
265             # Debug stuff
266             warn 'Stopped SimpleHTTP gracefully, no requests left' if DEBUG;
267             }
268              
269             # All done!
270             return 1;
271             }
272              
273             # Forcibly close all sockets that are open
274             foreach my $S ( $self->_requests, $self->_connections ) {
275             foreach my $conn ( keys %$S ) {
276              
277             # Can't call method "shutdown_input" on an undefined value at
278             # /usr/lib/perl5/site_perl/5.8.2/POE/Component/Server/SimpleHTTP.pm line 323.
279             if ( defined $S->{$conn}->wheel
280             and defined $S->{$conn}->wheel->get_input_handle() )
281             {
282             $S->{$conn}->close_wheel;
283             }
284              
285             # Delete this request
286             delete $S->{$conn};
287             }
288             }
289              
290             # Delete our alias
291             $kernel->alias_remove( $_ ) for $kernel->alias_list();
292             $kernel->refcount_decrement( $self->get_session_id, __PACKAGE__ )
293             unless $self->alias;
294              
295             # Debug stuff
296             warn 'Successfully stopped SimpleHTTP' if DEBUG;
297              
298             # Return success
299             return 1;
300             };
301              
302             # Sets up the SocketFactory wheel :)
303             event 'start_listener' => sub {
304             my ($kernel,$self,$noinc) = @_[KERNEL,OBJECT,ARG0];
305              
306             warn "Creating SocketFactory wheel now\n" if DEBUG;
307              
308             # Check if we should set up the wheel
309             if ( $self->retries == MAX_RETRIES ) {
310             die 'POE::Component::Server::SimpleHTTP tried '
311             . MAX_RETRIES
312             . ' times to create a Wheel and is giving up...';
313             }
314             else {
315              
316             $self->inc_retry unless $noinc;
317              
318             # Create our own SocketFactory Wheel :)
319             my $factory = POE::Wheel::SocketFactory->new(
320             BindPort => $self->port,
321             ( $self->address ? ( BindAddress => $self->address ) : () ),
322             Reuse => 'yes',
323             SuccessEvent => 'got_connection',
324             FailureEvent => 'listener_error',
325             );
326              
327             my ( $port, $address ) =
328             sockaddr_in( $factory->getsockname );
329             $self->_set_port( $port ) if $self->port == 0;
330              
331             $self->_set_factory( $factory );
332              
333             if ( $self->setuphandler ) {
334             my $setuphandler = $self->setuphandler;
335             if ( $setuphandler->{POSTBACK} and
336             ref $setuphandler->{POSTBACK} eq 'POE::Session::AnonEvent' ) {
337             $setuphandler->{POSTBACK}->( $port, $address );
338             }
339             else {
340             $kernel->post(
341             $setuphandler->{'SESSION'},
342             $setuphandler->{'EVENT'},
343             $port, $address,
344             ) if $setuphandler->{'SESSION'} and $setuphandler->{'EVENT'};
345             }
346             }
347             }
348              
349             return 1;
350             };
351              
352             # Got some sort of error from SocketFactory
353             event listener_error => sub {
354             my ($kernel,$self,$operation,$errnum,$errstr,$wheel_id) = @_[KERNEL,OBJECT,ARG0..ARG3];
355             warn
356             "SocketFactory Wheel $wheel_id generated $operation error $errnum: $errstr\n"
357             if DEBUG;
358              
359             $self->call( 'start_listener' );
360             return 1;
361             };
362              
363             # 'STARTLISTEN'
364             # Starts listening on the socket
365             event 'STARTLISTEN' => sub {
366             warn 'STARTLISTEN called, resuming accepts on SocketFactory'
367             if DEBUG;
368             $_[OBJECT]->call( 'start_listener', 'noinc' );
369             return 1;
370             };
371              
372             # 'STOPLISTEN'
373             # Stops listening on the socket
374             event 'STOPLISTEN' => sub {
375             my $self = $_[OBJECT];
376             warn 'STOPLISTEN called, pausing accepts on SocketFactory'
377             if DEBUG;
378             $self->_clear_factory if $self->_factory;
379             return 1;
380             };
381              
382             # 'SETHANDLERS'
383             # Sets the HANDLERS
384             event 'SETHANDLERS' => sub {
385             my ($self,$handlers) = @_[OBJECT,ARG0];
386             MassageHandlers($handlers);
387             $self->_set_handlers( $handlers );
388             return 1;
389             };
390              
391             # 'GETHANDLERS'
392             # Gets the HANDLERS
393             event 'GETHANDLERS' => sub {
394             my ($kernel,$self,$session,$event ) = @_[KERNEL,OBJECT,ARG0,ARG1];
395             return unless $session and $event;
396             require Storable;
397             my $handlers = Storable::dclone( $self->handlers );
398             delete $_->{'RE'} for @{ $handlers };
399             $kernel->post( $session, $event, $handlers );
400             return 1;
401             };
402              
403             # This subroutine massages the HANDLERS for internal use
404             # Should probably support POSTBACK/CALLBACK
405             sub MassageHandlers {
406             my $handler = shift;
407              
408             # Make sure it is ref to array
409             if ( !ref $handler or ref($handler) ne 'ARRAY' ) {
410             croak("HANDLERS is not a ref to an array!");
411             }
412              
413             # Massage the handlers
414             my $count = 0;
415             while ( $count < scalar(@$handler) ) {
416              
417             # Must be ref to hash
418             if ( ref $handler->[$count] and ref( $handler->[$count] ) eq 'HASH' ) {
419              
420             # Make sure all the keys are uppercase
421             $handler->[$count]->{ uc $_ } = delete $handler->[$count]->{$_}
422             for keys %{ $handler->[$count] };
423              
424             # Make sure it got the 3 parts necessary
425             if ( !exists $handler->[$count]->{'SESSION'}
426             or !defined $handler->[$count]->{'SESSION'} )
427             {
428             croak("HANDLER number $count does not have a SESSION argument!");
429             }
430             if ( !exists $handler->[$count]->{'EVENT'}
431             or !defined $handler->[$count]->{'EVENT'} )
432             {
433             croak("HANDLER number $count does not have an EVENT argument!");
434             }
435             if ( !exists $handler->[$count]->{'DIR'}
436             or !defined $handler->[$count]->{'DIR'} )
437             {
438             croak("HANDLER number $count does not have a DIR argument!");
439             }
440              
441             # Convert SESSION to ID
442             if (
443             UNIVERSAL::isa( $handler->[$count]->{'SESSION'}, 'POE::Session' ) )
444             {
445             $handler->[$count]->{'SESSION'} =
446             $handler->[$count]->{'SESSION'}->ID;
447             }
448              
449             # Convert DIR to qr// format
450             my $regex = undef;
451             eval { $regex = qr/$handler->[ $count ]->{'DIR'}/ };
452              
453             # Check for errors
454             if ($@) {
455             croak("HANDLER number $count has a malformed DIR -> $@");
456             }
457             else {
458              
459             # Store it!
460             $handler->[$count]->{'RE'} = $regex;
461             }
462             }
463             else {
464             croak("HANDLER number $count is not a reference to a HASH!");
465             }
466              
467             # Done with this one!
468             $count++;
469             }
470              
471             # Got here, success!
472             return 1;
473             }
474              
475             # 'Got_Connection'
476             # The actual manager of connections
477             event 'got_connection' => sub {
478             my ($kernel,$self,$socket,$peeraddr,$peerport) = @_[KERNEL,OBJECT,ARG0..ARG2];
479              
480              
481             # Should we SSLify it?
482             if ( $self->sslkeycert ) {
483              
484             # SSLify it!
485             eval { $socket = Server_SSLify($socket) };
486             if ($@) {
487             warn "Unable to turn on SSL for connection from "
488             . Socket::inet_ntoa( $peeraddr )
489             . " -> $@";
490             close $socket;
491             return 1;
492             }
493             }
494              
495             # Set up the Wheel to read from the socket
496             my $wheel = POE::Wheel::ReadWrite->new(
497             Handle => $socket,
498             Filter => POE::Filter::HTTPD->new(),
499             InputEvent => 'got_input',
500             FlushedEvent => 'got_flush',
501             ErrorEvent => 'got_error',
502             );
503              
504             if ( DEBUG and keys %{ $self->_connections } ) {
505              
506             # use Data::Dumper;
507             warn "conn id=", $wheel->ID, " [",
508             join( ', ', keys %{ $self->_connections } ), "]";
509             }
510              
511             # Save this wheel!
512             # 0 = wheel, 1 = Output done?, 2 = SimpleHTTP::Response object, 3 == request, 4 == streaming?
513             $self->_requests->{ $wheel->ID } =
514             POE::Component::Server::SimpleHTTP::State->new( wheel => $wheel );
515              
516             # Debug stuff
517             if (DEBUG) {
518             warn "Got_Connection completed creation of ReadWrite wheel ( "
519             . $wheel->ID . " )";
520             }
521              
522             # Success!
523             return 1;
524             };
525              
526             # 'Got_Input'
527             # Finally got input, set some stuff and send away!
528             event 'got_input' => sub {
529             my ($kernel,$self,$request,$id) = @_[KERNEL,OBJECT,ARG0,ARG1];
530             my $connection;
531              
532             # This whole thing is a mess. Keep-Alive was bolted on and it
533             # shows. Streaming is unpredictable. There are checks everywhere
534             # because it leaks wheels. *sigh*
535              
536             # Was this request Keep-Alive?
537             if ( $self->_connections->{$id} ) {
538             my $state = delete $self->_connections->{$id};
539             $state->reset;
540             $connection = $state->connection;
541             $state->clear_connection;
542             $self->_requests->{$id} = $state;
543             warn "Keep-alive id=$id next request..." if DEBUG;
544             }
545              
546             # Quick check to see if the socket died already...
547             # Initially reported by Tim Wood
548             unless ( $self->_requests->{$id}->wheel_alive ) {
549             warn 'Got a request, but socket died already!' if DEBUG;
550             # Destroy this wheel!
551             $self->_requests->{$id}->close_wheel;
552             delete $self->_requests->{$id};
553             return;
554             }
555              
556             SWITCH: {
557              
558             last SWITCH if $connection; # connection was kept-alive
559              
560             # Directly access POE::Wheel::ReadWrite's HANDLE_INPUT -> to get the socket itself
561             # Hmm, if we are SSL, then have to do an extra step!
562             if ( $self->sslkeycert ) {
563             $connection = POE::Component::Server::SimpleHTTP::Connection->new(
564             SSLify_GetSocket(
565             $self->_requests->{$id}->wheel->get_input_handle() )
566             );
567             last SWITCH;
568             }
569             $connection = POE::Component::Server::SimpleHTTP::Connection->new(
570             $self->_requests->{$id}->wheel->get_input_handle()
571             );
572             }
573              
574             # The HTTP::Response object, the path
575             my ( $response, $path, $malformed_req );
576              
577             # Check if it is HTTP::Request or Response
578             # Quoting POE::Filter::HTTPD
579             # The HTTPD filter parses the first HTTP 1.0 request from an incoming stream into an
580             # HTTP::Request object (if the request is good) or an HTTP::Response object (if the
581             # request was malformed).
582              
583             if ( $request->isa('HTTP::Response') ) {
584             # Make the request nothing
585             $response = $request;
586             $request = undef;
587              
588             # Mark that this is a malformed request
589             $malformed_req = 1;
590              
591             # Hack it to simulate POE::Component::Server::SimpleHTTP::Response->new( $id, $conn );
592             bless( $response, 'POE::Component::Server::SimpleHTTP::Response' );
593             $response->_WHEEL( $id );
594              
595             $response->set_connection( $connection );
596              
597             # Set the path to an empty string
598             $path = '';
599             }
600             else {
601             unless ( $self->proxymode ) {
602             # Add stuff it needs!
603             my $uri = $request->uri;
604             $uri->scheme('http');
605             $uri->host( $self->hostname );
606             $uri->port( $self->port );
607              
608             # Get the path
609             $path = $uri->path();
610             if ( !defined $path or $path eq '' ) {
611             # Make it the default handler
612             $path = '/';
613             }
614             }
615             else {
616             # We're in PROXYMODE set the path to the full URI
617             $path = $request->uri->as_string();
618             }
619              
620             # Get the response
621             $response =
622             POE::Component::Server::SimpleHTTP::Response->new( $id, $connection );
623              
624             # Stuff the default headers
625             $response->header( %{ $self->headers } )
626             if keys( %{ $self->headers } ) != 0;
627             }
628              
629             # Check if the SimpleHTTP::Connection object croaked ( happens when sockets just disappear )
630             unless ( defined $response->connection ) {
631             # Debug stuff
632             warn "could not make connection object" if DEBUG;
633             # Destroy this wheel!
634             $self->_requests->{$id}->close_wheel;
635             delete $self->_requests->{$id};
636             return;
637             }
638              
639             # If we used SSL, turn on the flag!
640             if ( $self->sslkeycert ) {
641             $response->connection->ssl(1);
642              
643             # Put the cipher type for people who want it
644             $response->connection->sslcipher(
645             SSLify_GetCipher( $self->_requests->{$id}->wheel->get_input_handle() )
646             );
647             }
648              
649             if ( !defined( $request ) ) {
650             $self->_requests->{$id}->close_wheel;
651             delete $self->_requests->{$id};
652             return;
653             }
654              
655             # Add this response to the wheel
656             $self->_requests->{$id}->set_response( $response );
657             $self->_requests->{$id}->set_request( $request );
658             $response->connection->ID($id);
659              
660             # If they have a log handler registered, send out the needed information
661             # TODO if we received a malformed request, we will not have a request object
662             # We need to figure out what we're doing because they can't always expect to have
663             # a request object, or should we keep it from being ?undef'd?
664              
665             if ( $self->loghandler and scalar keys %{ $self->loghandler } == 2 ) {
666             $! = undef;
667             $kernel->post(
668             $self->loghandler->{'SESSION'},
669             $self->loghandler->{'EVENT'},
670             $request, $response->connection->remote_ip()
671             );
672              
673             # Warn if we had a problem dispatching to the log handler above
674             warn(
675             "I had a problem posting to event '",
676             $self->loghandler->{'EVENT'},
677             "' of the log handler alias '",
678             $self->loghandler->{'SESSION'},
679             "'. As reported by Kernel: '$!', perhaps the alias is spelled incorrectly for this handler?"
680             ) if $!;
681             }
682              
683             # If we received a malformed request then
684             # let's not try to dispatch to a handler
685              
686             if ($malformed_req) {
687             # Just push out the response we got from POE::Filter::HTTPD saying your request was bad
688             $kernel->post(
689             $self->errorhandler->{SESSION},
690             $self->errorhandler->{EVENT},
691             'BadRequest (by POE::Filter::HTTPD)',
692             $response->connection->remote_ip()
693             ) if $self->errorhandler and $self->errorhandler->{SESSION} and $self->errorhandler->{EVENT};
694             $kernel->yield( 'DONE', $response );
695             return;
696             }
697              
698             # Find which handler will handle this one
699             foreach my $handler ( @{ $self->handlers } ) {
700              
701             # Check if this matches
702             if ( $path =~ $handler->{'RE'} ) {
703              
704             # Send this off!
705             $kernel->post( $handler->{'SESSION'}, $handler->{'EVENT'}, $request,
706             $response, $handler->{'DIR'}, );
707              
708             # Make sure we croak if we have an issue posting
709             croak(
710             "I had a problem posting to event $handler->{'EVENT'} of session $handler->{'SESSION'} for DIR handler '$handler->{'DIR'}'",
711             ". As reported by Kernel: '$!', perhaps the session name is spelled incorrectly for this handler?"
712             ) if $!;
713              
714             # All done!
715             return;
716             }
717             }
718              
719             # If we reached here, no handler was able to handle it...
720             # Set response code to 404 and tell the client we didn't find anything
721             $response->code(404);
722             $response->content('404 Not Found');
723             $kernel->yield( 'DONE', $response );
724             return;
725             };
726              
727             # 'Got_Flush'
728             # Finished with a request!
729             event 'got_flush' => sub {
730             my ($kernel,$self,$id) = @_[KERNEL,OBJECT,ARG0];
731              
732             return unless defined $self->_requests->{$id};
733              
734             # Debug stuff
735             warn "Got Flush event for wheel ID ( $id )" if DEBUG;
736              
737             if ( $self->_requests->{$id}->streaming ) {
738             # Do the stream !
739             warn "Streaming in progress ...!" if DEBUG;
740             return;
741             }
742              
743             # Check if we are shutting down
744             if ( $self->_requests->{$id}->done ) {
745              
746             if ( $self->must_keepalive( $id ) ) {
747             warn "Keep-alive id=$id ..." if DEBUG;
748             my $state = delete $self->_requests->{$id};
749             $state->set_connection( $state->response->connection );
750             $state->reset;
751             $self->_connections->{$id} = $state;
752             delete $self->_chunkcount->{$id};
753             delete $self->_responses->{$id};
754             }
755             else {
756             # Shutdown read/write on the wheel
757             $self->_requests->{$id}->close_wheel;
758             delete $self->_requests->{$id};
759             }
760              
761             }
762             else {
763              
764             # Ignore this, eh?
765             if (DEBUG) {
766             warn
767             "Got Flush event for socket ( $id ) when we did not send anything!";
768             }
769             }
770              
771             # Alright, do we have to shutdown?
772             unless ( $self->_factory ) {
773             # Check to see if we have any more requests
774             if ( keys( %{ $self->_requests } ) == 0
775             and keys( %{ $self->_connections } ) == 0 )
776             {
777             # Shutdown!
778             $kernel->yield('SHUTDOWN');
779             }
780             }
781              
782             # Success!
783             return 1;
784             };
785              
786             # should we keep-alive the connection?
787             sub must_keepalive {
788             my ( $self, $id ) = @_;
789              
790             return unless $self->keepalive;
791              
792             my $resp = $self->_requests->{$id}->response;
793             my $req = $self->_requests->{$id}->request;
794              
795             # error = close
796             return 0 if $resp->is_error;
797              
798             # Connection is a comma-seperated header
799             my $conn = lc $req->header('Connection');
800             return 0 if ",$conn," =~ /,\s*close\s*,/;
801             $conn = lc $req->header('Proxy-Connection');
802             return 0 if ",$conn," =~ /,\s*close\s*,/;
803             $conn = lc $resp->header('Connection');
804             return 0 if ",$conn," =~ /,\s*close\s*,/;
805              
806             # HTTP/1.1 = keep
807             return 1 if $req->protocol eq 'HTTP/1.1';
808             return 0;
809             }
810              
811             # 'Got_Error'
812             # Got some sort of error from ReadWrite
813             event 'got_error' => sub {
814             my ($kernel,$self,$operation,$errnum,$errstr,$id) = @_[KERNEL,OBJECT,ARG0..ARG3];
815              
816             # Only do this for non-EOF on read
817             #unless ( $operation eq 'read' and $errnum == 0 ) {
818             {
819              
820             # Debug stuff
821             warn "Wheel $id generated $operation error $errnum: $errstr\n"
822             if DEBUG;
823              
824             my $connection;
825             if ( $self->_connections->{$id} ) {
826             my $c = delete $self->_connections->{$id};
827             $connection = $c->connection;
828             $c->close_wheel;
829             }
830             else {
831              
832             if( defined $self->_requests->{$id}->response ) {
833             $connection = $self->_requests->{$id}->response->connection;
834             }
835             else {
836             warn "response for $id is undefined" if DEBUG;
837             }
838              
839             # Delete this connection
840             $self->_requests->{$id}->close_wheel;
841             }
842              
843             delete $self->_requests->{$id};
844             delete $self->_responses->{$id};
845              
846             # Mark the client dead
847             $connection->dead(1) if $connection;
848             }
849              
850             # Success!
851             return 1;
852             };
853              
854             # 'DONE'
855             # Output to the client!
856             event 'DONE' => sub {
857             my ($kernel,$self,$response) = @_[KERNEL,OBJECT,ARG0];
858              
859             # Check if we got it
860             if ( !defined $response or !UNIVERSAL::isa( $response, 'HTTP::Response' ) ) {
861             warn 'Did not get a HTTP::Response object!' if DEBUG;
862             # Abort...
863             return;
864             }
865              
866             # Get the wheel ID
867             my $id = $response->_WHEEL;
868              
869             # Check if the wheel exists ( sometimes it gets closed by the client, but the application doesn't know that... )
870             unless ( exists $self->_requests->{$id} ) {
871              
872             # Debug stuff
873             warn
874             'Wheel disappeared, but the application sent us a DONE event, discarding it'
875             if DEBUG;
876              
877             $kernel->post(
878             $self->errorhandler->{SESSION},
879             $self->errorhandler->{EVENT},
880             'Wheel disappeared !'
881             ) if $self->errorhandler and $self->errorhandler->{SESSION} and $self->errorhandler->{EVENT};
882              
883             # All done!
884             return 1;
885             }
886              
887              
888             # Check if we have already sent the response
889             if ( $self->_requests->{$id}->done ) {
890             # Tried to send twice!
891             die 'Tried to send a response to the same connection twice!';
892             }
893              
894             # Quick check to see if the wheel/socket died already...
895             # Initially reported by Tim Wood
896             unless ( $self->_requests->{$id}->wheel_alive ) {
897             warn 'Tried to send data over a closed/nonexistant socket!' if DEBUG;
898             $kernel->post(
899             $self->errorhandler->{SESSION},
900             $self->errorhandler->{EVENT},
901             'Socket closed/nonexistant !'
902             ) if $self->errorhandler and $self->errorhandler->{SESSION} and $self->errorhandler->{EVENT};
903             return;
904             }
905              
906             # Check if we were streaming.
907              
908             if ( $self->_requests->{$id}->streaming ) {
909             $self->_requests->{$id}->set_streaming(0);
910             $self->_requests->{$id}->set_done(1); # Finished streaming
911             # TODO: We might not get a flush, trigger it ourselves.
912             if ( !$self->_requests->{$id}->wheel->get_driver_out_messages ) {
913             $kernel->yield( 'got_flush', $id );
914             }
915             return;
916             }
917            
918              
919             $self->fix_headers( $response );
920              
921             # Send it out!
922             $self->_requests->{$id}->wheel->put($response);
923              
924             # Mark this socket done
925             $self->_requests->{$id}->set_done(1);
926              
927             # Log FINALLY If they have a logFinal handler registered, send out the needed information
928             if ( $self->log2handler and scalar keys %{ $self->log2handler } == 2 ) {
929             $! = undef;
930             $kernel->call(
931             $self->log2handler->{'SESSION'},
932             $self->log2handler->{'EVENT'},
933             $self->_requests->{$id}->request, $response
934             );
935              
936             # Warn if we had a problem dispatching to the log handler above
937             warn(
938             "I had a problem posting to event '",
939             $self->log2handler->{'EVENT'},
940             "' of the log handler alias '",
941             $self->log2handler->{'SESSION'},
942             "'. As reported by Kernel: '$!', perhaps the alias is spelled incorrectly for this handler?"
943             ) if $!;
944             }
945              
946             # Debug stuff
947             warn "Completed with Wheel ID $id" if DEBUG;
948              
949             # Success!
950             return 1;
951             };
952              
953             # 'STREAM'
954             # Stream output to the client
955             event 'STREAM' => sub {
956             my ($kernel,$self,$response) = @_[KERNEL,OBJECT,ARG0];
957              
958             # Check if we got it
959             unless ( defined $response and UNIVERSAL::isa( $response, 'HTTP::Response' ) ) {
960             warn 'Did not get a HTTP::Response object!' if DEBUG;
961             # Abort...
962             return;
963             }
964              
965             # Get the wheel ID
966             my $id = $response->_WHEEL;
967             $self->_chunkcount->{$id}++;
968              
969             if ( defined $response->STREAM ) {
970              
971             # Keep track if we plan to stream ...
972             if ( $self->_responses->{$id} ) {
973             warn "Restoring response from HEAP and id $id " if DEBUG;
974             $response = $self->_responses->{$id};
975             }
976             else {
977             warn "Saving HEAP response to id $id " if DEBUG;
978             $self->_responses->{$id} = $response;
979             }
980             }
981             else {
982             warn
983             'Can\'t push on a response that has not been not set as a STREAM!'
984             if DEBUG;
985             # Abort...
986             return;
987             }
988              
989             # Check if the wheel exists ( sometimes it gets closed by the client, but the application doesn't know that... )
990             unless ( exists $self->_requests->{$id} ) {
991              
992             # Debug stuff
993             warn
994             'Wheel disappeared, but the application sent us a DONE event, discarding it'
995             if DEBUG;
996              
997             $kernel->post(
998             $self->errorhandler->{SESSION},
999             $self->errorhandler->{EVENT},
1000             'Wheel disappeared !'
1001             ) if $self->errorhandler and $self->errorhandler->{SESSION} and $self->errorhandler->{EVENT};
1002              
1003             # All done!
1004             return 1;
1005             }
1006              
1007             # Quick check to see if the wheel/socket died already...
1008             # Initially reported by Tim Wood
1009             unless ( $self->_requests->{$id}->wheel_alive ) {
1010             warn 'Tried to send data over a closed/nonexistant socket!' if DEBUG;
1011             $kernel->post(
1012             $self->errorhandler->{SESSION},
1013             $self->errorhandler->{EVENT},
1014             'Socket closed/nonexistant !'
1015             ) if $self->errorhandler and $self->errorhandler->{SESSION} and $self->errorhandler->{EVENT};
1016             return;
1017             }
1018              
1019             $self->fix_headers( $response, 1 );
1020              
1021             # Sets the correct POE::Filter
1022             unless ( defined $response->IS_STREAMING ) {
1023              
1024             # Mark this socket done
1025             $self->_requests->{$id}->set_streaming(1);
1026             $response->set_streaming(1);
1027             }
1028              
1029             if (DEBUG) {
1030             warn "Sending stream via "
1031             . $response->STREAM_SESSION . "/"
1032             . $response->STREAM
1033             . " with id $id \n";
1034             }
1035              
1036             if ( $self->_chunkcount->{$id} > 1 ) {
1037             my $wheel = $self->_requests->{ $response->_WHEEL }->wheel;
1038             $wheel->set_output_filter( POE::Filter::Stream->new() );
1039             $wheel->put( $response->content );
1040             }
1041             else {
1042             my $wheel = $self->_requests->{ $response->_WHEEL }->wheel;
1043             $wheel->set_output_filter( $wheel->get_input_filter() );
1044             $wheel->put($response);
1045             }
1046              
1047             # we send the event to stream with wheels request and response to the session
1048             # that has registered the streaming event
1049             unless ( $response->DONT_FLUSH ) {
1050             $kernel->post(
1051             $response->STREAM_SESSION, # callback session
1052             $response->STREAM, # callback event
1053             $self->_responses->{ $response->_WHEEL }
1054             );
1055             }
1056              
1057             # Success!
1058             return 1;
1059             };
1060              
1061             # Add required headers to a response
1062             sub fix_headers {
1063             my ( $self, $response, $stream ) = @_;
1064              
1065             # Set the date if needed
1066             if ( !$response->header('Date') ) {
1067             $response->header( 'Date', time2str(time) );
1068             }
1069              
1070             # Set the Content-Length if needed
1071             if ( !$stream and !$self->proxymode
1072             and !defined $response->header('Content-Length')
1073             and my $len = length $response->content )
1074             {
1075             use bytes;
1076             $response->header( 'Content-Length', $len );
1077             }
1078              
1079             # Set the Content-Type if needed
1080             if ( !$response->header('Content-Type') ) {
1081             $response->header( 'Content-Type', 'text/plain' );
1082             }
1083              
1084             if ( !$response->protocol ) {
1085             my $request = $self->_requests->{ $response->_WHEEL }->request;
1086             return unless $request and $request->isa('HTTP::Request');
1087             unless ( $request->method eq 'HEAD' ) {
1088             $response->protocol( $request->protocol );
1089             }
1090             }
1091             }
1092              
1093             # 'CLOSE'
1094             # Closes the connection
1095             event 'CLOSE' => sub {
1096             my ($kernel,$self,$response) = @_[KERNEL,OBJECT,ARG0];
1097              
1098             # Check if we got it
1099             unless ( defined $response and UNIVERSAL::isa( $response, 'HTTP::Response' ) ) {
1100             warn 'Did not get a HTTP::Response object!' if DEBUG;
1101             # Abort...
1102             return;
1103             }
1104              
1105             # Get the wheel ID
1106             my $id = $response->_WHEEL;
1107              
1108             if ( $self->_connections->{$id} ) {
1109             $self->_requests->{$id} = delete $self->_connections->{$id};
1110             }
1111              
1112             # Check if the wheel exists ( sometimes it gets closed by the client, but the application doesn't know that... )
1113             unless ( exists $self->_requests->{$id} ) {
1114             warn
1115             'Wheel disappeared, but the application sent us a CLOSE event, discarding it'
1116             if DEBUG;
1117             return 1;
1118             }
1119              
1120             # Kill it!
1121             $self->_requests->{$id}->close_wheel if $self->_requests->{$id}->wheel_alive;
1122              
1123             # Delete it!
1124             delete $self->_requests->{$id};
1125             delete $self->_responses->{$id};
1126              
1127             warn 'Delete references to the connection done.' if DEBUG;
1128              
1129             # All done!
1130             return 1;
1131             };
1132              
1133             # Registers a POE inline state (primarly for streaming)
1134             event 'REGISTER' => sub {
1135             my ( $session, $state, $code_ref ) = @_[ SESSION, ARG0 .. ARG1 ];
1136             warn 'Registering state in POE session' if DEBUG;
1137             return $session->register_state( $state, $code_ref );
1138             };
1139              
1140             # SETCLOSEHANDLER
1141             event 'SETCLOSEHANDLER' => sub {
1142             my ($self,$sender) = @_[OBJECT,SENDER ];
1143             my ($connection,$state,@params) = @_[ARG0..$#_];
1144              
1145             # turn connection ID into the connection object
1146             unless ( ref $connection ) {
1147             my $id = $connection;
1148             if ( $self->_connections->{$id} ) {
1149             $connection = $self->_connections->{$id}->connection;
1150             }
1151             elsif ($self->_requests->{$id}
1152             and $self->_requests->{$id}->response )
1153             {
1154             $connection = $self->_requests->{$id}->response->connection;
1155             }
1156             unless ( ref $connection ) {
1157             die "Can't find connection object for request $id";
1158             }
1159             }
1160              
1161             if ($state) {
1162             $connection->_on_close( $sender->ID, $state, @params );
1163             }
1164             else {
1165             $connection->_on_close($sender->ID);
1166             }
1167             };
1168              
1169             no MooseX::POE;
1170              
1171             __PACKAGE__->meta->make_immutable( );
1172              
1173             "Simple In'it";
1174              
1175             __END__
1176              
1177             =head1 NAME
1178              
1179             POE::Component::Server::SimpleHTTP - Perl extension to serve HTTP requests in POE.
1180              
1181             =head1 SYNOPSIS
1182              
1183             use POE;
1184             use POE::Component::Server::SimpleHTTP;
1185              
1186             # Start the server!
1187             POE::Component::Server::SimpleHTTP->new(
1188             'ALIAS' => 'HTTPD',
1189             'PORT' => 11111,
1190             'HOSTNAME' => 'MySite.com',
1191             'HANDLERS' => [
1192             {
1193             'DIR' => '^/bar/.*',
1194             'SESSION' => 'HTTP_GET',
1195             'EVENT' => 'GOT_BAR',
1196             },
1197             {
1198             'DIR' => '^/$',
1199             'SESSION' => 'HTTP_GET',
1200             'EVENT' => 'GOT_MAIN',
1201             },
1202             {
1203             'DIR' => '^/foo/.*',
1204             'SESSION' => 'HTTP_GET',
1205             'EVENT' => 'GOT_NULL',
1206             },
1207             {
1208             'DIR' => '.*',
1209             'SESSION' => 'HTTP_GET',
1210             'EVENT' => 'GOT_ERROR',
1211             },
1212             ],
1213              
1214             'LOGHANDLER' => { 'SESSION' => 'HTTP_GET',
1215             'EVENT' => 'GOT_LOG',
1216             },
1217              
1218             'LOG2HANDLER' => { 'SESSION' => 'HTTP_GET',
1219             'EVENT' => 'POSTLOG',
1220             },
1221              
1222             # In the testing phase...
1223             'SSLKEYCERT' => [ 'private-key.pem', 'public-cert.pem' ],
1224             'SSLINTERMEDIATECACERT' => 'intermediate-ca-cert.pem',
1225             ) or die 'Unable to create the HTTP Server';
1226              
1227             # Create our own session to receive events from SimpleHTTP
1228             POE::Session->create(
1229             inline_states => {
1230             '_start' => sub { $_[KERNEL]->alias_set( 'HTTP_GET' );
1231             $_[KERNEL]->post( 'HTTPD', 'GETHANDLERS', $_[SESSION], 'GOT_HANDLERS' );
1232             },
1233              
1234             'GOT_BAR' => \&GOT_REQ,
1235             'GOT_MAIN' => \&GOT_REQ,
1236             'GOT_ERROR' => \&GOT_ERR,
1237             'GOT_NULL' => \&GOT_NULL,
1238             'GOT_HANDLERS' => \&GOT_HANDLERS,
1239             'GOT_LOG' => \&GOT_LOG,
1240             },
1241             );
1242              
1243             # Start POE!
1244             POE::Kernel->run();
1245              
1246             sub GOT_HANDLERS {
1247             # ARG0 = HANDLERS array
1248             my $handlers = $_[ ARG0 ];
1249              
1250             # Move the first handler to the last one
1251             push( @$handlers, shift( @$handlers ) );
1252              
1253             # Send it off!
1254             $_[KERNEL]->post( 'HTTPD', 'SETHANDLERS', $handlers );
1255             }
1256              
1257             sub GOT_NULL {
1258             # ARG0 = HTTP::Request object, ARG1 = HTTP::Response object, ARG2 = the DIR that matched
1259             my( $request, $response, $dirmatch ) = @_[ ARG0 .. ARG2 ];
1260              
1261             # Kill this!
1262             $_[KERNEL]->post( 'HTTPD', 'CLOSE', $response );
1263             }
1264              
1265             sub GOT_REQ {
1266             # ARG0 = HTTP::Request object, ARG1 = HTTP::Response object, ARG2 = the DIR that matched
1267             my( $request, $response, $dirmatch ) = @_[ ARG0 .. ARG2 ];
1268              
1269             # Do our stuff to HTTP::Response
1270             $response->code( 200 );
1271             $response->content( 'Some funky HTML here' );
1272              
1273             # We are done!
1274             # For speed, you could use $_[KERNEL]->call( ... )
1275             $_[KERNEL]->post( 'HTTPD', 'DONE', $response );
1276             }
1277              
1278             sub GOT_ERR {
1279             # ARG0 = HTTP::Request object, ARG1 = HTTP::Response object, ARG2 = the DIR that matched
1280             my( $request, $response, $dirmatch ) = @_[ ARG0 .. ARG2 ];
1281              
1282             # Check for errors
1283             if ( ! defined $request ) {
1284             $_[KERNEL]->post( 'HTTPD', 'DONE', $response );
1285             return;
1286             }
1287              
1288             # Do our stuff to HTTP::Response
1289             $response->code( 404 );
1290             $response->content( "Hi visitor from " . $response->connection->remote_ip . ", Page not found -> '" . $request->uri->path . "'" );
1291              
1292             # We are done!
1293             # For speed, you could use $_[KERNEL]->call( ... )
1294             $_[KERNEL]->post( 'HTTPD', 'DONE', $response );
1295             }
1296              
1297             sub GOT_LOG {
1298             # ARG0 = HTTP::Request object, ARG1 = remote IP
1299             my ($request, $remote_ip) = @_[ARG0,ARG1];
1300              
1301             # Do some sort of logging activity.
1302             # If the request was malformed, $request = undef
1303             # CHECK FOR A REQUEST OBJECT BEFORE USING IT.
1304             if( $request ) {
1305             {
1306             warn join(' ', time(), $remote_ip, $request->uri ), "\n";
1307             } else {
1308             warn join(' ', time(), $remote_ip, 'Bad request' ), "\n";
1309             }
1310              
1311             return;
1312             }
1313              
1314             =head1 ABSTRACT
1315              
1316             An easy to use HTTP daemon for POE-enabled programs
1317              
1318             =head1 DESCRIPTION
1319              
1320             This module makes serving up HTTP requests a breeze in POE.
1321              
1322             The hardest thing to understand in this module is the HANDLERS. That's it!
1323              
1324             The standard way to use this module is to do this:
1325              
1326             use POE;
1327             use POE::Component::Server::SimpleHTTP;
1328              
1329             POE::Component::Server::SimpleHTTP->new( ... );
1330              
1331             POE::Session->create( ... );
1332              
1333             POE::Kernel->run();
1334              
1335             =head2 Starting SimpleHTTP
1336              
1337             To start SimpleHTTP, just call it's new method:
1338              
1339             POE::Component::Server::SimpleHTTP->new(
1340             'ALIAS' => 'HTTPD',
1341             'ADDRESS' => '192.168.1.1',
1342             'PORT' => 11111,
1343             'HOSTNAME' => 'MySite.com',
1344             'HEADERS' => {},
1345             'HANDLERS' => [ ],
1346             );
1347              
1348             This method will die on error or return success.
1349              
1350             This constructor accepts only 7 options.
1351              
1352             =over 4
1353              
1354             =item C<ALIAS>
1355              
1356             This will set the alias SimpleHTTP uses in the POE Kernel.
1357             This will default to "SimpleHTTP"
1358              
1359             =item C<ADDRESS>
1360              
1361             This value will be passed to POE::Wheel::SocketFactory to bind to, will use INADDR_ANY if it is nothing is provided.
1362              
1363             =item C<PORT>
1364              
1365             This value will be passed to POE::Wheel::SocketFactory to bind to.
1366              
1367             =item C<HOSTNAME>
1368              
1369             This value is for the HTTP::Request's URI to point to.
1370             If this is not supplied, SimpleHTTP will use Sys::Hostname to find it.
1371              
1372             =item C<HEADERS>
1373              
1374             This should be a hashref, that will become the default headers on all HTTP::Response objects.
1375             You can override this in individual requests by setting it via $request->header( ... )
1376              
1377             For more information, consult the L<HTTP::Headers> module.
1378              
1379             =item C<HANDLERS>
1380              
1381             This is the hardest part of SimpleHTTP :)
1382              
1383             You supply an array, with each element being a hash. All the hashes should contain those 3 keys:
1384              
1385             DIR -> The regexp that will be used, more later.
1386              
1387             SESSION -> The session to send the input
1388              
1389             EVENT -> The event to trigger
1390              
1391             The DIR key should be a valid regexp. This will be matched against the current request path.
1392             Pseudocode is: if ( $path =~ /$DIR/ )
1393              
1394             NOTE: The path is UNIX style, not MSWIN style ( /blah/foo not \blah\foo )
1395              
1396             Now, if you supply 100 handlers, how will SimpleHTTP know what to do? Simple! By passing in an array in the first place,
1397             you have already told SimpleHTTP the order of your handlers. They will be tried in order, and if a match is not found,
1398             SimpleHTTP will return a 404 response.
1399              
1400             This allows some cool things like specifying 3 handlers with DIR of:
1401             '^/foo/.*', '^/$', '.*'
1402              
1403             Now, if the request is not in /foo or not root, your 3rd handler will catch it, becoming the "404 not found" handler!
1404              
1405             NOTE: You might get weird Session/Events, make sure your handlers are in order, for example: '^/', '^/foo/.*'
1406             The 2nd handler will NEVER get any requests, as the first one will match ( no $ in the regex )
1407              
1408             Now, here's what a handler receives:
1409              
1410             ARG0 -> HTTP::Request object
1411              
1412             ARG1 -> POE::Component::Server::SimpleHTTP::Response object
1413              
1414             ARG2 -> The exact DIR that matched, so you can see what triggered what
1415              
1416             NOTE: If ARG0 is undef, that means POE::Filter::HTTPD encountered an error parsing the client request, simply modify the HTTP::Response
1417             object and send some sort of generic error. SimpleHTTP will set the path used in matching the DIR regexes to an empty string, so if there
1418             is a "catch-all" DIR regex like '.*', it will catch the errors, and only that one.
1419              
1420             NOTE: The only way SimpleHTTP will leak memory ( hopefully heh ) is if you discard the SimpleHTTP::Response object without sending it
1421             back to SimpleHTTP via the DONE/CLOSE events, so never do that!
1422              
1423             =item C<KEEPALIVE>
1424              
1425             Set to true to enable HTTP keep-alive support. Connections will be
1426             kept alive until the client closes the connection. All HTTP/1.1 connections
1427             are kept-open, unless you set the response C<Connection> header to C<close>.
1428              
1429             $response->header( Connection => 'close' );
1430              
1431             If you want more control, use L<POE::Component::Server::HTTP::KeepAlive>.
1432              
1433             =item C<LOGHANDLER>
1434              
1435             Expects a hashref with the following key, values:
1436              
1437             SESSION -> The session to send the input
1438              
1439             EVENT -> The event to trigger
1440              
1441             You will receive an event for each request to the server from clients. Malformed client requests will not be passed into the handler. Instead
1442             undef will be passed.
1443             Event is called before ANY content handler is called.
1444              
1445             The event will have the following parameters:
1446              
1447             ARG0 -> HTTP::Request object/undef if client request was malformed.
1448              
1449             ARG1 -> the IP address of the client
1450              
1451             =item C<LOG2HANDLER>
1452              
1453             Expect a hashref with the following key, valyes:
1454              
1455             SESSION -> The session to send the input
1456              
1457             EVENT -> The event to trigger
1458              
1459             You will receive an event for each response that hit DONE call. Malformed client requests will not be passed into the handler.
1460             Event is after processing all content handlers.
1461              
1462             The event will have the following parameters:
1463              
1464             ARG0 -> HTTP::Request object
1465              
1466             ARG1 -> HTTP::Response object
1467              
1468             That makes possible following code:
1469              
1470             my ($login, $password) = $request->authorization_basic();
1471             printf STDERR "%s - %s [%s] \"%s %s %s\" %d %d\n",
1472             $response->connection->remote_ip, $login||'-', POSIX::strftime("%d/%b/%Y:%T %z",localtime(time())),
1473             $request->method(), $request->uri()->path(), $request->protocol(),
1474             $response->code(), length($response->content());
1475              
1476             Emulate apache-like logs for PoCo::Server::SimpleHTTP
1477              
1478             =item C<SETUPHANDLER>
1479              
1480             Expects a hashref with the following key, values:
1481              
1482             SESSION -> The session to send the input
1483              
1484             EVENT -> The event to trigger
1485              
1486             You will receive an event when the listener wheel has been setup.
1487              
1488             Currently there are no parameters returned.
1489              
1490             =item C<SSLKEYCERT>
1491              
1492             This should be an arrayref of only 2 elements - the private key and public certificate locations. Now, this is still in the experimental stage, and testing
1493             is greatly welcome!
1494              
1495             Again, this will automatically turn every incoming connection into a SSL socket. Once enough testing has been done, this option will be augmented with more SSL stuff!
1496              
1497             =item C<SSLINTERMEDIATECACERT>
1498              
1499             This option is needed in case the SSL certificate references an intermediate certification authority certificate.
1500              
1501             =item C<PROXYMODE>
1502              
1503             Set this to a true value to enable the server to act as a proxy server, ie. it won't mangle the HTTP::Request
1504             URI.
1505              
1506             =back
1507              
1508             =head2 Events
1509              
1510             SimpleHTTP is so simple, there are only 8 events available.
1511              
1512             =over 4
1513              
1514             =item C<DONE>
1515              
1516             This event accepts only one argument: the HTTP::Response object we sent to the handler.
1517              
1518             Calling this event implies that this particular request is done, and will proceed to close the socket.
1519              
1520             NOTE: This method automatically sets those 3 headers if they are not already set:
1521             Date -> Current date stringified via HTTP::Date->time2str
1522             Content-Type -> text/html
1523             Content-Length -> length( $response->content )
1524              
1525             To get greater throughput and response time, do not post() to the DONE event, call() it!
1526             However, this will force your program to block while servicing web requests...
1527              
1528             =item C<CLOSE>
1529              
1530             This event accepts only one argument: the HTTP::Response object we sent to the handler.
1531              
1532             Calling this event will close the socket, not sending any output
1533              
1534             =item C<GETHANDLERS>
1535              
1536             This event accepts 2 arguments: The session + event to send the response to
1537              
1538             This event will send back the current HANDLERS array ( deep-cloned via Storable::dclone )
1539              
1540             The resulting array can be played around to your tastes, then once you are done...
1541              
1542             =item C<SETHANDLERS>
1543              
1544             This event accepts only one argument: pointer to HANDLERS array
1545              
1546             BEWARE: if there is an error in the HANDLERS, SimpleHTTP will die!
1547              
1548             =item C<SETCLOSEHANDLER>
1549              
1550             $_[KERNEL]->call( $_[SENDER], 'SETCLOSEHANDLER', $connection,
1551             $event, @args );
1552              
1553             Calls C<$event> in the current session when C<$connection> is closed. You
1554             could use for persistent connection handling.
1555              
1556             Multiple session may register close handlers.
1557              
1558             Calling SETCLOSEHANDLER without C<$event> to remove the current session's
1559             handler:
1560              
1561             $_[KERNEL]->call( $_[SENDER], 'SETCLOSEHANDLER', $connection );
1562              
1563             You B<must> make sure that C<@args> doesn't cause a circular
1564             reference. Ideally, use C<$connection->ID> or some other unique value
1565             associated with this C<$connection>.
1566              
1567             =item C<STARTLISTEN>
1568              
1569             Starts the listening socket, if it was shut down
1570              
1571             =item C<STOPLISTEN>
1572              
1573             Simply a wrapper for SHUTDOWN GRACEFUL, but will not shutdown SimpleHTTP if there is no more requests
1574              
1575             =item C<SHUTDOWN>
1576              
1577             Without arguments, SimpleHTTP does this:
1578             Close the listening socket
1579             Kills all pending requests by closing their sockets
1580             Removes it's alias
1581              
1582             With an argument of 'GRACEFUL', SimpleHTTP does this:
1583             Close the listening socket
1584             Waits for all pending requests to come in via DONE/CLOSE, then removes it's alias
1585              
1586             =item C<STREAM>
1587              
1588             With a $response argument it streams the content and calls back the streaming event
1589             of the user's session (or with the dont_flush option you're responsible for calling
1590             back your session's streaming event).
1591              
1592             To use the streaming feature see below.
1593              
1594             =back
1595              
1596             =head2 Streaming with SimpleHTTP
1597              
1598             It's possible to send data as a stream to clients (unbuffered and integrated in the
1599             POE loop).
1600              
1601             Just create your session to receive events from SimpleHTTP as usually and add a
1602             streaming event, this event will be triggered over and over each time you set the
1603             $response to a streaming state and once you trigger it:
1604              
1605             # sets the response as streamed within our session which alias is HTTP_GET
1606             # with the event GOT_STREAM
1607             $response->stream(
1608             session => 'HTTP_GET',
1609             event => 'GOT_STREAM',
1610             dont_flush => 1
1611             );
1612              
1613             # then you can simply yield your streaming event, once the GOT_STREAM event
1614             # has reached its end it will be triggered again and again, until you
1615             # send a CLOSE event to the kernel with the appropriate response as parameter
1616             $kernel->yield('GOT_STREAM', $response);
1617              
1618             The optionnal dont_flush option gives the user the ability to control the callback
1619             to the streaming event, which means once your stream event has reached its end
1620             it won't be called, you have to call it back.
1621              
1622             You can now send data by chunks and either call yourself back (via POE) or
1623             shutdown when your streaming is done (EOF for example).
1624              
1625             sub GOT_STREAM {
1626             my ( $kernel, $heap, $response ) = @_[KERNEL, HEAP, ARG0];
1627              
1628             # sets the content of the response
1629             $response->content("Hello World\n");
1630              
1631             # send it to the client
1632             POE::Kernel->post('HTTPD', 'STREAM', $response);
1633              
1634             # if we have previously set the dont_flush option
1635             # we have to trigger our event back until the end of
1636             # the stream like this (that can be a yield, of course):
1637             #
1638             # $kernel->delay('GOT_STREAM', 1, $stream );
1639              
1640             # otherwise the GOT_STREAM event is triggered continously until
1641             # we call the CLOSE event on the response like that :
1642             #
1643             if ($heap{'streaming_is_done'}) {
1644             # close the socket and end the stream
1645             POE::Kernel->post('HTTPD', 'CLOSE', $response );
1646             }
1647             }
1648              
1649             The dont_flush option is there to be able to control the frequency of flushes
1650             to the client.
1651              
1652             =head2 SimpleHTTP Notes
1653              
1654             You can enable debugging mode by doing this:
1655              
1656             sub POE::Component::Server::SimpleHTTP::DEBUG () { 1 }
1657             use POE::Component::Server::SimpleHTTP;
1658              
1659             Also, this module will try to keep the Listening socket alive.
1660             if it dies, it will open it again for a max of 5 retries.
1661              
1662             You can override this behavior by doing this:
1663              
1664             sub POE::Component::Server::SimpleHTTP::MAX_RETRIES () { 10 }
1665             use POE::Component::Server::SimpleHTTP;
1666              
1667             For those who are pondering about basic-authentication, here's a tiny snippet to put in the Event handler
1668              
1669             # Contributed by Rocco Caputo
1670             sub Got_Request {
1671             # ARG0 = HTTP::Request, ARG1 = HTTP::Response
1672             my( $request, $response ) = @_[ ARG0, ARG1 ];
1673              
1674             # Get the login
1675             my ( $login, $password ) = $request->authorization_basic();
1676              
1677             # Decide what to do
1678             if ( ! defined $login or ! defined $password ) {
1679             # Set the authorization
1680             $response->header( 'WWW-Authenticate' => 'Basic realm="MyRealm"' );
1681             $response->code( 401 );
1682             $response->content( 'FORBIDDEN.' );
1683              
1684             # Send it off!
1685             $_[KERNEL]->post( 'SimpleHTTP', 'DONE', $response );
1686             } else {
1687             # Authenticate the user and move on
1688             }
1689             }
1690              
1691             =head2 EXPORT
1692              
1693             Nothing.
1694              
1695             =head1 SEE ALSO
1696              
1697             L<POE>
1698              
1699             L<POE::Filter::HTTPD>
1700              
1701             L<HTTP::Request>
1702              
1703             L<HTTP::Response>
1704              
1705             L<POE::Component::Server::SimpleHTTP::Connection>
1706              
1707             L<POE::Component::Server::SimpleHTTP::Response>
1708              
1709             L<POE::Component::Server::SimpleHTTP::PreFork>
1710              
1711             L<POE::Component::SSLify>
1712              
1713             =head1 AUTHOR
1714              
1715             Apocalypse E<lt>apocal@cpan.orgE<gt>
1716              
1717             =head1 COPYRIGHT AND LICENSE
1718              
1719             Copyright 2006 by Apocalypse
1720              
1721             This library is free software; you can redistribute it and/or modify
1722             it under the same terms as Perl itself.
1723              
1724             =cut