File Coverage

blib/lib/POE/Component/Server/SimpleHTTP.pm
Criterion Covered Total %
statement 230 424 54.2
branch 72 196 36.7
condition 17 96 17.7
subroutine 32 44 72.7
pod 1 9 11.1
total 352 769 45.7


line stmt bran cond sub pod time code
1             package POE::Component::Server::SimpleHTTP;
2              
3 7     7   868913 use strict;
  7         17  
  7         210  
4 7     7   39 use warnings;
  7         14  
  7         265  
5              
6 7     7   35 use vars qw($VERSION);
  7         15  
  7         338  
7              
8             $VERSION = '2.20';
9              
10 7     7   38 use POE;
  7         12  
  7         74  
11 7     7   9508 use POE::Wheel::SocketFactory;
  7         62191  
  7         234  
12 7     7   5604 use POE::Wheel::ReadWrite;
  7         58658  
  7         222  
13 7     7   6819 use POE::Filter::HTTPD;
  7         284501  
  7         247  
14 7     7   5228 use POE::Filter::Stream;
  7         2338  
  7         210  
15              
16 7     7   40 use Carp qw( croak );
  7         17  
  7         375  
17 7     7   95 use Socket;
  7         15  
  7         5206  
18              
19 7     7   37 use HTTP::Date qw( time2str );
  7         11  
  7         284  
20              
21 7     7   3706 use POE::Component::Server::SimpleHTTP::Connection;
  7         26  
  7         398  
22 7     7   5770 use POE::Component::Server::SimpleHTTP::Response;
  7         25  
  7         301  
23 7     7   4850 use POE::Component::Server::SimpleHTTP::State;
  7         25  
  7         507  
24              
25             BEGIN {
26              
27             # Debug fun!
28 7 50   7   46 if ( !defined &DEBUG ) {
29 7         270 eval "sub DEBUG () { 0 }";
30             }
31              
32             # Our own definition of the max retries
33 7 50       39 if ( !defined &MAX_RETRIES ) {
34 7         352 eval "sub MAX_RETRIES () { 5 }";
35             }
36             }
37              
38 7     7   6612 use MooseX::POE;
  7         10312  
  7         32  
39 7     7   1714826 use Moose::Util::TypeConstraints;
  7         410  
  7         85  
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 6     6 1 18 my $class = shift;
174 6         53 my %args = @_;
175 6         102 $args{lc $_} = delete $args{$_} for keys %args;
176              
177 6 0 33     44 if ( $args{sslkeycert} and ref $args{sslkeycert} eq 'ARRAY'
      33        
178 0         0 and scalar @{ $args{sslkeycert} } == 2 ) {
179              
180 0         0 eval {
181 0         0 require POE::Component::SSLify;
182 0         0 import POE::Component::SSLify
183             qw( SSLify_Options SSLify_GetSocket Server_SSLify SSLify_GetCipher SSLify_GetCTX );
184 0         0 SSLify_Options( @{ $args{sslkeycert} } );
  0         0  
185             };
186 0 0       0 if ($@) {
187 0         0 warn "Unable to load PoCo::SSLify -> $@" if DEBUG;
188 0         0 delete $args{sslkeycert};
189             }
190             else {
191 0 0       0 if ( $args{sslintermediatecacert} ) {
192 0         0 my $ctx = SSLify_GetCTX();
193 0         0 Net::SSLeay::CTX_load_verify_locations($ctx, $args{sslintermediatecacert}, '');
194             }
195             }
196             }
197              
198 6         200 return $class->SUPER::BUILDARGS(%args);
199             }
200              
201             sub session_id {
202 0     0 0 0 shift->get_session_id;
203             }
204              
205             sub getsockname {
206 0     0 0 0 shift->_factory->getsockname;
207             }
208              
209             sub shutdown {
210 0     0 0 0 my $self = shift;
211 0         0 $poe_kernel->call( $self->get_session_id, 'SHUTDOWN', @_ );
212             }
213              
214             # This subroutine, when SimpleHTTP exits, will search for leaks
215             sub STOP {
216 6     6 0 2907 my $self = $_[OBJECT];
217             # Loop through all of the requests
218 6         20 foreach my $req ( keys %{ $self->_requests } ) {
  6         305  
219              
220             # Bite the programmer!
221             warn 'Did not get DONE/CLOSE event for Wheel ID '
222             . $req
223             . ' from IP '
224 0         0 . $self->_requests->{$req}->response->connection->remote_ip;
225             }
226              
227             # All done!
228 6         25 return 1;
229             }
230              
231             sub START {
232 6     6 0 5301 my ($kernel,$self) = @_[KERNEL,OBJECT];
233 6 50       315 $kernel->alias_set( $self->alias ) if $self->alias;
234 6 50       483 $kernel->refcount_increment( $self->get_session_id, __PACKAGE__ )
235             unless $self->alias;
236 6         263 MassageHandlers( $self->handlers );
237             # Start Listener
238 6         26 $kernel->yield( 'start_listener' );
239 6         311 return;
240             }
241              
242             # 'SHUTDOWN'
243             # Stops the server!
244             event 'SHUTDOWN' => sub {
245 6     6   25373 my ($kernel,$self,$graceful) = @_[KERNEL,OBJECT,ARG0];
246             # Shutdown the SocketFactory wheel
247 6 50       387 $self->_clear_factory if $self->_factory;
248              
249             # Debug stuff
250 6         37 warn 'Stopped listening for new connections!' if DEBUG;
251              
252             # Are we gracefully shutting down or not?
253 6 50       1340 if ( $graceful ) {
254              
255             # Check for number of requests
256 0 0       0 if ( keys( %{ $self->_requests } ) == 0 ) {
  0         0  
257              
258             # Alright, shutdown anyway
259              
260             # Delete our alias
261 0         0 $kernel->alias_remove( $_ ) for $kernel->alias_list();
262 0 0       0 $kernel->refcount_decrement( $self->get_session_id, __PACKAGE__ )
263             unless $self->alias;
264              
265             # Debug stuff
266 0         0 warn 'Stopped SimpleHTTP gracefully, no requests left' if DEBUG;
267             }
268              
269             # All done!
270 0         0 return 1;
271             }
272              
273             # Forcibly close all sockets that are open
274 6         309 foreach my $S ( $self->_requests, $self->_connections ) {
275 12         44 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 0 0 0     0 if ( defined $S->{$conn}->wheel
280             and defined $S->{$conn}->wheel->get_input_handle() )
281             {
282 0         0 $S->{$conn}->close_wheel;
283             }
284              
285             # Delete this request
286 0         0 delete $S->{$conn};
287             }
288             }
289              
290             # Delete our alias
291 6         35 $kernel->alias_remove( $_ ) for $kernel->alias_list();
292 6 50       748 $kernel->refcount_decrement( $self->get_session_id, __PACKAGE__ )
293             unless $self->alias;
294              
295             # Debug stuff
296 6         16 warn 'Successfully stopped SimpleHTTP' if DEBUG;
297              
298             # Return success
299 6         26 return 1;
300             };
301              
302             # Sets up the SocketFactory wheel :)
303             event 'start_listener' => sub {
304 6     6   1275 my ($kernel,$self,$noinc) = @_[KERNEL,OBJECT,ARG0];
305              
306 6         13 warn "Creating SocketFactory wheel now\n" if DEBUG;
307              
308             # Check if we should set up the wheel
309 6 50       310 if ( $self->retries == MAX_RETRIES ) {
310 0         0 die 'POE::Component::Server::SimpleHTTP tried '
311             . MAX_RETRIES
312             . ' times to create a Wheel and is giving up...';
313             }
314             else {
315              
316 6 50       358 $self->inc_retry unless $noinc;
317              
318             # Create our own SocketFactory Wheel :)
319 6 50       257 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 6         3155 my ( $port, $address ) =
328             sockaddr_in( $factory->getsockname );
329 6 50       459 $self->_set_port( $port ) if $self->port == 0;
330              
331 6         288 $self->_set_factory( $factory );
332              
333 6 50       275 if ( $self->setuphandler ) {
334 6         272 my $setuphandler = $self->setuphandler;
335 6 50 33     43 if ( $setuphandler->{POSTBACK} and
336             ref $setuphandler->{POSTBACK} eq 'POE::Session::AnonEvent' ) {
337 0         0 $setuphandler->{POSTBACK}->( $port, $address );
338             }
339             else {
340             $kernel->post(
341             $setuphandler->{'SESSION'},
342             $setuphandler->{'EVENT'},
343             $port, $address,
344 6 50 33     157 ) if $setuphandler->{'SESSION'} and $setuphandler->{'EVENT'};
345             }
346             }
347             }
348              
349 6         634 return 1;
350             };
351              
352             # Got some sort of error from SocketFactory
353             event listener_error => sub {
354 0     0   0 my ($kernel,$self,$operation,$errnum,$errstr,$wheel_id) = @_[KERNEL,OBJECT,ARG0..ARG3];
355 0         0 warn
356             "SocketFactory Wheel $wheel_id generated $operation error $errnum: $errstr\n"
357             if DEBUG;
358              
359 0         0 $self->call( 'start_listener' );
360 0         0 return 1;
361             };
362              
363             # 'STARTLISTEN'
364             # Starts listening on the socket
365             event 'STARTLISTEN' => sub {
366 0     0   0 warn 'STARTLISTEN called, resuming accepts on SocketFactory'
367             if DEBUG;
368 0         0 $_[OBJECT]->call( 'start_listener', 'noinc' );
369 0         0 return 1;
370             };
371              
372             # 'STOPLISTEN'
373             # Stops listening on the socket
374             event 'STOPLISTEN' => sub {
375 0     0   0 my $self = $_[OBJECT];
376 0         0 warn 'STOPLISTEN called, pausing accepts on SocketFactory'
377             if DEBUG;
378 0 0       0 $self->_clear_factory if $self->_factory;
379 0         0 return 1;
380             };
381              
382             # 'SETHANDLERS'
383             # Sets the HANDLERS
384             event 'SETHANDLERS' => sub {
385 0     0   0 my ($self,$handlers) = @_[OBJECT,ARG0];
386 0         0 MassageHandlers($handlers);
387 0         0 $self->_set_handlers( $handlers );
388 0         0 return 1;
389             };
390              
391             # 'GETHANDLERS'
392             # Gets the HANDLERS
393             event 'GETHANDLERS' => sub {
394 0     0   0 my ($kernel,$self,$session,$event ) = @_[KERNEL,OBJECT,ARG0,ARG1];
395 0 0 0     0 return unless $session and $event;
396 0         0 require Storable;
397 0         0 my $handlers = Storable::dclone( $self->handlers );
398 0         0 delete $_->{'RE'} for @{ $handlers };
  0         0  
399 0         0 $kernel->post( $session, $event, $handlers );
400 0         0 return 1;
401             };
402              
403             # This subroutine massages the HANDLERS for internal use
404             # Should probably support POSTBACK/CALLBACK
405             sub MassageHandlers {
406 6     6 0 13 my $handler = shift;
407              
408             # Make sure it is ref to array
409 6 50 33     68 if ( !ref $handler or ref($handler) ne 'ARRAY' ) {
410 0         0 croak("HANDLERS is not a ref to an array!");
411             }
412              
413             # Massage the handlers
414 6         573 my $count = 0;
415 6         31 while ( $count < scalar(@$handler) ) {
416              
417             # Must be ref to hash
418 18 50 33     190 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 18         25 for keys %{ $handler->[$count] };
  18         149  
423              
424             # Make sure it got the 3 parts necessary
425 18 50 33     97 if ( !exists $handler->[$count]->{'SESSION'}
426             or !defined $handler->[$count]->{'SESSION'} )
427             {
428 0         0 croak("HANDLER number $count does not have a SESSION argument!");
429             }
430 18 50 33     90 if ( !exists $handler->[$count]->{'EVENT'}
431             or !defined $handler->[$count]->{'EVENT'} )
432             {
433 0         0 croak("HANDLER number $count does not have an EVENT argument!");
434             }
435 18 50 33     100 if ( !exists $handler->[$count]->{'DIR'}
436             or !defined $handler->[$count]->{'DIR'} )
437             {
438 0         0 croak("HANDLER number $count does not have a DIR argument!");
439             }
440              
441             # Convert SESSION to ID
442 18 50       86 if (
443             UNIVERSAL::isa( $handler->[$count]->{'SESSION'}, 'POE::Session' ) )
444             {
445             $handler->[$count]->{'SESSION'} =
446 0         0 $handler->[$count]->{'SESSION'}->ID;
447             }
448              
449             # Convert DIR to qr// format
450 18         27 my $regex = undef;
451 18         26 eval { $regex = qr/$handler->[ $count ]->{'DIR'}/ };
  18         258  
452              
453             # Check for errors
454 18 50       50 if ($@) {
455 0         0 croak("HANDLER number $count has a malformed DIR -> $@");
456             }
457             else {
458              
459             # Store it!
460 18         38 $handler->[$count]->{'RE'} = $regex;
461             }
462             }
463             else {
464 0         0 croak("HANDLER number $count is not a reference to a HASH!");
465             }
466              
467             # Done with this one!
468 18         46 $count++;
469             }
470              
471             # Got here, success!
472 6         13 return 1;
473             }
474              
475             # 'Got_Connection'
476             # The actual manager of connections
477             event 'got_connection' => sub {
478 9     9   33050 my ($kernel,$self,$socket,$peeraddr,$peerport) = @_[KERNEL,OBJECT,ARG0..ARG2];
479              
480              
481             # Should we SSLify it?
482 9 50       524 if ( $self->sslkeycert ) {
483              
484             # SSLify it!
485 0         0 eval { $socket = Server_SSLify($socket) };
  0         0  
486 0 0       0 if ($@) {
487 0         0 warn "Unable to turn on SSL for connection from "
488             . Socket::inet_ntoa( $peeraddr )
489             . " -> $@";
490 0         0 close $socket;
491 0         0 return 1;
492             }
493             }
494              
495             # Set up the Wheel to read from the socket
496 9         115 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 9         3218 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 9         503 $self->_requests->{ $wheel->ID } =
514             POE::Component::Server::SimpleHTTP::State->new( wheel => $wheel );
515              
516             # Debug stuff
517 9         54 if (DEBUG) {
518             warn "Got_Connection completed creation of ReadWrite wheel ( "
519             . $wheel->ID . " )";
520             }
521              
522             # Success!
523 9         37 return 1;
524             };
525              
526             # 'Got_Input'
527             # Finally got input, set some stuff and send away!
528             event 'got_input' => sub {
529 12     12   65417 my ($kernel,$self,$request,$id) = @_[KERNEL,OBJECT,ARG0,ARG1];
530 12         26 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 12 100       618 if ( $self->_connections->{$id} ) {
538 3         113 my $state = delete $self->_connections->{$id};
539 3         14 $state->reset;
540 3         166 $connection = $state->connection;
541 3         138 $state->clear_connection;
542 3         113 $self->_requests->{$id} = $state;
543 3         7 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 12 50       503 unless ( $self->_requests->{$id}->wheel_alive ) {
549 0         0 warn 'Got a request, but socket died already!' if DEBUG;
550             # Destroy this wheel!
551 0         0 $self->_requests->{$id}->close_wheel;
552 0         0 delete $self->_requests->{$id};
553 0         0 return;
554             }
555              
556             SWITCH: {
557              
558 12 100       30 last SWITCH if $connection; # connection was kept-alive
  12         40  
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 9 50       406 if ( $self->sslkeycert ) {
563             $connection = POE::Component::Server::SimpleHTTP::Connection->new(
564             SSLify_GetSocket(
565 0         0 $self->_requests->{$id}->wheel->get_input_handle() )
566             );
567 0         0 last SWITCH;
568             }
569             $connection = POE::Component::Server::SimpleHTTP::Connection->new(
570 9         398 $self->_requests->{$id}->wheel->get_input_handle()
571             );
572             }
573              
574             # The HTTP::Response object, the path
575 12         29 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 12 50       115 if ( $request->isa('HTTP::Response') ) {
584             # Make the request nothing
585 0         0 $response = $request;
586 0         0 $request = undef;
587              
588             # Mark that this is a malformed request
589 0         0 $malformed_req = 1;
590              
591             # Hack it to simulate POE::Component::Server::SimpleHTTP::Response->new( $id, $conn );
592 0         0 bless( $response, 'POE::Component::Server::SimpleHTTP::Response' );
593 0         0 $response->_WHEEL( $id );
594              
595 0         0 $response->set_connection( $connection );
596              
597             # Set the path to an empty string
598 0         0 $path = '';
599             }
600             else {
601 12 50       548 unless ( $self->proxymode ) {
602             # Add stuff it needs!
603 12         49 my $uri = $request->uri;
604 12         116 $uri->scheme('http');
605 12         22525 $uri->host( $self->hostname );
606 12         1705 $uri->port( $self->port );
607              
608             # Get the path
609 12         645 $path = $uri->path();
610 12 50 33     209 if ( !defined $path or $path eq '' ) {
611             # Make it the default handler
612 0         0 $path = '/';
613             }
614             }
615             else {
616             # We're in PROXYMODE set the path to the full URI
617 0         0 $path = $request->uri->as_string();
618             }
619              
620             # Get the response
621 12         140 $response =
622             POE::Component::Server::SimpleHTTP::Response->new( $id, $connection );
623              
624             # Stuff the default headers
625 0         0 $response->header( %{ $self->headers } )
626 12 50       7782 if keys( %{ $self->headers } ) != 0;
  12         519  
627             }
628              
629             # Check if the SimpleHTTP::Connection object croaked ( happens when sockets just disappear )
630 12 50       564 unless ( defined $response->connection ) {
631             # Debug stuff
632 0         0 warn "could not make connection object" if DEBUG;
633             # Destroy this wheel!
634 0         0 $self->_requests->{$id}->close_wheel;
635 0         0 delete $self->_requests->{$id};
636 0         0 return;
637             }
638              
639             # If we used SSL, turn on the flag!
640 12 50       514 if ( $self->sslkeycert ) {
641 0         0 $response->connection->ssl(1);
642              
643             # Put the cipher type for people who want it
644             $response->connection->sslcipher(
645 0         0 SSLify_GetCipher( $self->_requests->{$id}->wheel->get_input_handle() )
646             );
647             }
648              
649 12 50       45 if ( !defined( $request ) ) {
650 0         0 $self->_requests->{$id}->close_wheel;
651 0         0 delete $self->_requests->{$id};
652 0         0 return;
653             }
654              
655             # Add this response to the wheel
656 12         551 $self->_requests->{$id}->set_response( $response );
657 12         567 $self->_requests->{$id}->set_request( $request );
658 12         523 $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 12 50 33     525 if ( $self->loghandler and scalar keys %{ $self->loghandler } == 2 ) {
  12         512  
666 0         0 $! = undef;
667             $kernel->post(
668             $self->loghandler->{'SESSION'},
669 0         0 $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 0 0       0 $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 12 50       47 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 0 0 0     0 ) if $self->errorhandler and $self->errorhandler->{SESSION} and $self->errorhandler->{EVENT};
      0        
694 0         0 $kernel->yield( 'DONE', $response );
695 0         0 return;
696             }
697              
698             # Find which handler will handle this one
699 12         26 foreach my $handler ( @{ $self->handlers } ) {
  12         521  
700              
701             # Check if this matches
702 32 100       190 if ( $path =~ $handler->{'RE'} ) {
703              
704             # Send this off!
705             $kernel->post( $handler->{'SESSION'}, $handler->{'EVENT'}, $request,
706 9         83 $response, $handler->{'DIR'}, );
707              
708             # Make sure we croak if we have an issue posting
709 9 50       1171 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 9         50 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 3         15 $response->code(404);
722 3         41 $response->content('404 Not Found');
723 3         96 $kernel->yield( 'DONE', $response );
724 3         213 return;
725             };
726              
727             # 'Got_Flush'
728             # Finished with a request!
729             event 'got_flush' => sub {
730 12     12   3621 my ($kernel,$self,$id) = @_[KERNEL,OBJECT,ARG0];
731              
732 12 50       552 return unless defined $self->_requests->{$id};
733              
734             # Debug stuff
735 12         24 warn "Got Flush event for wheel ID ( $id )" if DEBUG;
736              
737 12 50       537 if ( $self->_requests->{$id}->streaming ) {
738             # Do the stream !
739 0         0 warn "Streaming in progress ...!" if DEBUG;
740 0         0 return;
741             }
742              
743             # Check if we are shutting down
744 12 50       566 if ( $self->_requests->{$id}->done ) {
745              
746 12 100       56 if ( $self->must_keepalive( $id ) ) {
747 3         34 warn "Keep-alive id=$id ..." if DEBUG;
748 3         127 my $state = delete $self->_requests->{$id};
749 3         142 $state->set_connection( $state->response->connection );
750 3         12 $state->reset;
751 3         113 $self->_connections->{$id} = $state;
752 3         118 delete $self->_chunkcount->{$id};
753 3         118 delete $self->_responses->{$id};
754             }
755             else {
756             # Shutdown read/write on the wheel
757 9         404 $self->_requests->{$id}->close_wheel;
758 9         423 delete $self->_requests->{$id};
759             }
760              
761             }
762             else {
763              
764             # Ignore this, eh?
765 0         0 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 12 50       525 unless ( $self->_factory ) {
773             # Check to see if we have any more requests
774 0 0 0     0 if ( keys( %{ $self->_requests } ) == 0
  0         0  
775 0         0 and keys( %{ $self->_connections } ) == 0 )
776             {
777             # Shutdown!
778 0         0 $kernel->yield('SHUTDOWN');
779             }
780             }
781              
782             # Success!
783 12         46 return 1;
784             };
785              
786             # should we keep-alive the connection?
787             sub must_keepalive {
788 12     12 0 27 my ( $self, $id ) = @_;
789              
790 12 100       505 return unless $self->keepalive;
791              
792 4         158 my $resp = $self->_requests->{$id}->response;
793 4         146 my $req = $self->_requests->{$id}->request;
794              
795             # error = close
796 4 100       25 return 0 if $resp->is_error;
797              
798             # Connection is a comma-seperated header
799 3         46 my $conn = lc $req->header('Connection');
800 3 50       117 return 0 if ",$conn," =~ /,\s*close\s*,/;
801 3         8 $conn = lc $req->header('Proxy-Connection');
802 3 50       434 return 0 if ",$conn," =~ /,\s*close\s*,/;
803 3         16 $conn = lc $resp->header('Connection');
804 3 50       226 return 0 if ",$conn," =~ /,\s*close\s*,/;
805              
806             # HTTP/1.1 = keep
807 3 50       11 return 1 if $req->protocol eq 'HTTP/1.1';
808 0         0 return 0;
809             }
810              
811             # 'Got_Error'
812             # Got some sort of error from ReadWrite
813             event 'got_error' => sub {
814 0     0   0 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 0         0 warn "Wheel $id generated $operation error $errnum: $errstr\n"
  0         0  
822             if DEBUG;
823              
824 0         0 my $connection;
825 0 0       0 if ( $self->_connections->{$id} ) {
826 0         0 my $c = delete $self->_connections->{$id};
827 0         0 $connection = $c->connection;
828 0         0 $c->close_wheel;
829             }
830             else {
831              
832 0 0       0 if( defined $self->_requests->{$id}->response ) {
833 0         0 $connection = $self->_requests->{$id}->response->connection;
834             }
835             else {
836 0         0 warn "response for $id is undefined" if DEBUG;
837             }
838              
839             # Delete this connection
840 0         0 $self->_requests->{$id}->close_wheel;
841             }
842              
843 0         0 delete $self->_requests->{$id};
844 0         0 delete $self->_responses->{$id};
845              
846             # Mark the client dead
847 0 0       0 $connection->dead(1) if $connection;
848             }
849              
850             # Success!
851 0         0 return 1;
852             };
853              
854             # 'DONE'
855             # Output to the client!
856             event 'DONE' => sub {
857 12     12   5704 my ($kernel,$self,$response) = @_[KERNEL,OBJECT,ARG0];
858              
859             # Check if we got it
860 12 50 33     96 if ( !defined $response or !UNIVERSAL::isa( $response, 'HTTP::Response' ) ) {
861 0         0 warn 'Did not get a HTTP::Response object!' if DEBUG;
862             # Abort...
863 0         0 return;
864             }
865              
866             # Get the wheel ID
867 12         635 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 12 50       491 unless ( exists $self->_requests->{$id} ) {
871              
872             # Debug stuff
873 0         0 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 0 0 0     0 ) if $self->errorhandler and $self->errorhandler->{SESSION} and $self->errorhandler->{EVENT};
      0        
882              
883             # All done!
884 0         0 return 1;
885             }
886              
887              
888             # Check if we have already sent the response
889 12 50       525 if ( $self->_requests->{$id}->done ) {
890             # Tried to send twice!
891 0         0 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 12 50       498 unless ( $self->_requests->{$id}->wheel_alive ) {
897 0         0 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 0 0 0     0 ) if $self->errorhandler and $self->errorhandler->{SESSION} and $self->errorhandler->{EVENT};
      0        
903 0         0 return;
904             }
905              
906             # Check if we were streaming.
907              
908 12 50       522 if ( $self->_requests->{$id}->streaming ) {
909 0         0 $self->_requests->{$id}->set_streaming(0);
910 0         0 $self->_requests->{$id}->set_done(1); # Finished streaming
911             # TODO: We might not get a flush, trigger it ourselves.
912 0 0       0 if ( !$self->_requests->{$id}->wheel->get_driver_out_messages ) {
913 0         0 $kernel->yield( 'got_flush', $id );
914             }
915 0         0 return;
916             }
917            
918              
919 12         65 $self->fix_headers( $response );
920              
921             # Send it out!
922 12         707 $self->_requests->{$id}->wheel->put($response);
923              
924             # Mark this socket done
925 12         4270 $self->_requests->{$id}->set_done(1);
926              
927             # Log FINALLY If they have a logFinal handler registered, send out the needed information
928 12 50 33     538 if ( $self->log2handler and scalar keys %{ $self->log2handler } == 2 ) {
  12         475  
929 0         0 $! = undef;
930             $kernel->call(
931             $self->log2handler->{'SESSION'},
932             $self->log2handler->{'EVENT'},
933 0         0 $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 0 0       0 $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 12         21 warn "Completed with Wheel ID $id" if DEBUG;
948              
949             # Success!
950 12         53 return 1;
951             };
952              
953             # 'STREAM'
954             # Stream output to the client
955             event 'STREAM' => sub {
956 0     0   0 my ($kernel,$self,$response) = @_[KERNEL,OBJECT,ARG0];
957              
958             # Check if we got it
959 0 0 0     0 unless ( defined $response and UNIVERSAL::isa( $response, 'HTTP::Response' ) ) {
960 0         0 warn 'Did not get a HTTP::Response object!' if DEBUG;
961             # Abort...
962 0         0 return;
963             }
964              
965             # Get the wheel ID
966 0         0 my $id = $response->_WHEEL;
967 0         0 $self->_chunkcount->{$id}++;
968              
969 0 0       0 if ( defined $response->STREAM ) {
970              
971             # Keep track if we plan to stream ...
972 0 0       0 if ( $self->_responses->{$id} ) {
973 0         0 warn "Restoring response from HEAP and id $id " if DEBUG;
974 0         0 $response = $self->_responses->{$id};
975             }
976             else {
977 0         0 warn "Saving HEAP response to id $id " if DEBUG;
978 0         0 $self->_responses->{$id} = $response;
979             }
980             }
981             else {
982 0         0 warn
983             'Can\'t push on a response that has not been not set as a STREAM!'
984             if DEBUG;
985             # Abort...
986 0         0 return;
987             }
988              
989             # Check if the wheel exists ( sometimes it gets closed by the client, but the application doesn't know that... )
990 0 0       0 unless ( exists $self->_requests->{$id} ) {
991              
992             # Debug stuff
993 0         0 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 0 0 0     0 ) if $self->errorhandler and $self->errorhandler->{SESSION} and $self->errorhandler->{EVENT};
      0        
1002              
1003             # All done!
1004 0         0 return 1;
1005             }
1006              
1007             # Quick check to see if the wheel/socket died already...
1008             # Initially reported by Tim Wood
1009 0 0       0 unless ( $self->_requests->{$id}->wheel_alive ) {
1010 0         0 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 0 0 0     0 ) if $self->errorhandler and $self->errorhandler->{SESSION} and $self->errorhandler->{EVENT};
      0        
1016 0         0 return;
1017             }
1018              
1019 0         0 $self->fix_headers( $response, 1 );
1020              
1021             # Sets the correct POE::Filter
1022 0 0       0 unless ( defined $response->IS_STREAMING ) {
1023              
1024             # Mark this socket done
1025 0         0 $self->_requests->{$id}->set_streaming(1);
1026 0         0 $response->set_streaming(1);
1027             }
1028              
1029 0         0 if (DEBUG) {
1030             warn "Sending stream via "
1031             . $response->STREAM_SESSION . "/"
1032             . $response->STREAM
1033             . " with id $id \n";
1034             }
1035              
1036 0 0       0 if ( $self->_chunkcount->{$id} > 1 ) {
1037 0         0 my $wheel = $self->_requests->{ $response->_WHEEL }->wheel;
1038 0         0 $wheel->set_output_filter( POE::Filter::Stream->new() );
1039 0         0 $wheel->put( $response->content );
1040             }
1041             else {
1042 0         0 my $wheel = $self->_requests->{ $response->_WHEEL }->wheel;
1043 0         0 $wheel->set_output_filter( $wheel->get_input_filter() );
1044 0         0 $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 0 0       0 unless ( $response->DONT_FLUSH ) {
1050             $kernel->post(
1051             $response->STREAM_SESSION, # callback session
1052             $response->STREAM, # callback event
1053 0         0 $self->_responses->{ $response->_WHEEL }
1054             );
1055             }
1056              
1057             # Success!
1058 0         0 return 1;
1059             };
1060              
1061             # Add required headers to a response
1062             sub fix_headers {
1063 12     12 0 43 my ( $self, $response, $stream ) = @_;
1064              
1065             # Set the date if needed
1066 12 50       107 if ( !$response->header('Date') ) {
1067 12         693 $response->header( 'Date', time2str(time) );
1068             }
1069              
1070             # Set the Content-Length if needed
1071 12 50 33     1323 if ( !$stream and !$self->proxymode
      33        
1072             and !defined $response->header('Content-Length')
1073             and my $len = length $response->content )
1074             {
1075 7     7   51777 use bytes;
  7         16  
  7         76  
1076 12         620 $response->header( 'Content-Length', $len );
1077             }
1078              
1079             # Set the Content-Type if needed
1080 12 100       498 if ( !$response->header('Content-Type') ) {
1081 5         184 $response->header( 'Content-Type', 'text/plain' );
1082             }
1083              
1084 12 50       485 if ( !$response->protocol ) {
1085 12         677 my $request = $self->_requests->{ $response->_WHEEL }->request;
1086 12 50 33     132 return unless $request and $request->isa('HTTP::Request');
1087 12 50       52 unless ( $request->method eq 'HEAD' ) {
1088 12         168 $response->protocol( $request->protocol );
1089             }
1090             }
1091             }
1092              
1093             # 'CLOSE'
1094             # Closes the connection
1095             event 'CLOSE' => sub {
1096 0     0   0 my ($kernel,$self,$response) = @_[KERNEL,OBJECT,ARG0];
1097              
1098             # Check if we got it
1099 0 0 0     0 unless ( defined $response and UNIVERSAL::isa( $response, 'HTTP::Response' ) ) {
1100 0         0 warn 'Did not get a HTTP::Response object!' if DEBUG;
1101             # Abort...
1102 0         0 return;
1103             }
1104              
1105             # Get the wheel ID
1106 0         0 my $id = $response->_WHEEL;
1107              
1108 0 0       0 if ( $self->_connections->{$id} ) {
1109 0         0 $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 0 0       0 unless ( exists $self->_requests->{$id} ) {
1114 0         0 warn
1115             'Wheel disappeared, but the application sent us a CLOSE event, discarding it'
1116             if DEBUG;
1117 0         0 return 1;
1118             }
1119              
1120             # Kill it!
1121 0 0       0 $self->_requests->{$id}->close_wheel if $self->_requests->{$id}->wheel_alive;
1122              
1123             # Delete it!
1124 0         0 delete $self->_requests->{$id};
1125 0         0 delete $self->_responses->{$id};
1126              
1127 0         0 warn 'Delete references to the connection done.' if DEBUG;
1128              
1129             # All done!
1130 0         0 return 1;
1131             };
1132              
1133             # Registers a POE inline state (primarly for streaming)
1134             event 'REGISTER' => sub {
1135 0     0   0 my ( $session, $state, $code_ref ) = @_[ SESSION, ARG0 .. ARG1 ];
1136 0         0 warn 'Registering state in POE session' if DEBUG;
1137 0         0 return $session->register_state( $state, $code_ref );
1138             };
1139              
1140             # SETCLOSEHANDLER
1141             event 'SETCLOSEHANDLER' => sub {
1142 2     2   143 my ($self,$sender) = @_[OBJECT,SENDER ];
1143 2         8 my ($connection,$state,@params) = @_[ARG0..$#_];
1144              
1145             # turn connection ID into the connection object
1146 2 50       8 unless ( ref $connection ) {
1147 2         5 my $id = $connection;
1148 2 50 33     87 if ( $self->_connections->{$id} ) {
    50          
1149 0         0 $connection = $self->_connections->{$id}->connection;
1150             }
1151             elsif ($self->_requests->{$id}
1152             and $self->_requests->{$id}->response )
1153             {
1154 2         77 $connection = $self->_requests->{$id}->response->connection;
1155             }
1156 2 50       10 unless ( ref $connection ) {
1157 0         0 die "Can't find connection object for request $id";
1158             }
1159             }
1160              
1161 2 50       7 if ($state) {
1162 2         11 $connection->_on_close( $sender->ID, $state, @params );
1163             }
1164             else {
1165 0           $connection->_on_close($sender->ID);
1166             }
1167             };
1168              
1169 7     7   4198 no MooseX::POE;
  7         20  
  7         69  
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, values:
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 optional 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 continuously 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