File Coverage

blib/lib/POE/Component/Server/SimpleHTTP.pm
Criterion Covered Total %
statement 227 422 53.7
branch 72 196 36.7
condition 20 105 19.0
subroutine 31 43 72.0
pod 1 9 11.1
total 351 775 45.2


line stmt bran cond sub pod time code
1             package POE::Component::Server::SimpleHTTP;
2             $POE::Component::Server::SimpleHTTP::VERSION = '2.26';
3             #ABSTRACT: Perl extension to serve HTTP requests in POE.
4              
5 6     6   574843 use strict;
  6         10  
  6         157  
6 6     6   29 use warnings;
  6         6  
  6         162  
7              
8 6     6   29 use POE;
  6         11  
  6         72  
9 6     6   4948 use POE::Wheel::SocketFactory;
  6         33520  
  6         158  
10 6     6   2721 use POE::Wheel::ReadWrite;
  6         36784  
  6         166  
11 6     6   2909 use POE::Filter::HTTPD;
  6         174100  
  6         213  
12 6     6   2486 use POE::Filter::Stream;
  6         1628  
  6         157  
13              
14 6     6   28 use Carp qw( croak );
  6         5  
  6         283  
15 6     6   24 use Socket;
  6         8  
  6         2845  
16              
17 6     6   95 use HTTP::Date qw( time2str );
  6         10  
  6         245  
18              
19 6     6   2413 use POE::Component::Server::SimpleHTTP::Connection;
  6         23  
  6         327  
20 6     6   3883 use POE::Component::Server::SimpleHTTP::Response;
  6         21  
  6         249  
21 6     6   3290 use POE::Component::Server::SimpleHTTP::State;
  6         18  
  6         383  
22              
23             BEGIN {
24              
25             # Debug fun!
26 6 50   6   25 if ( !defined &DEBUG ) {
27 6         192 eval "sub DEBUG () { 0 }";
28             }
29              
30             # Our own definition of the max retries
31 6 50       25 if ( !defined &MAX_RETRIES ) {
32 6         253 eval "sub MAX_RETRIES () { 5 }";
33             }
34             }
35              
36 6     6   3634 use MooseX::POE;
  6         6399  
  6         19  
37 6     6   982306 use Moose::Util::TypeConstraints;
  6         12  
  6         57  
38              
39              
40             has 'alias' => (
41             is => 'ro',
42             );
43              
44             has 'address' => (
45             is => 'ro',
46             );
47              
48             has 'port' => (
49             is => 'ro',
50             default => sub { 0 },
51             writer => '_set_port',
52             );
53              
54             has 'hostname' => (
55             is => 'ro',
56             default => sub { require Sys::Hostname; return Sys::Hostname::hostname(); },
57             );
58              
59             has 'proxymode' => (
60             is => 'ro',
61             isa => 'Bool',
62             default => sub { 0 },
63             );
64              
65             has 'keepalive' => (
66             is => 'ro',
67             isa => 'Bool',
68             default => sub { 0 },
69             );
70              
71             has 'sslkeycert' => (
72             is => 'ro',
73             isa => subtype 'ArrayRef' => where { scalar @$_ == 2 },
74             );
75              
76             has 'sslintermediatecacert' => (
77             is => 'ro',
78             isa => 'Str',
79             );
80              
81             has 'headers' => (
82             is => 'ro',
83             isa => 'HashRef',
84             default => sub {{}},
85             );
86              
87             has 'handlers' => (
88             is => 'ro',
89             isa => 'ArrayRef',
90             required => 1,
91             writer => '_set_handlers',
92             );
93              
94             has 'errorhandler' => (
95             is => 'ro',
96             isa => 'HashRef',
97             default => sub {{}},
98             );
99              
100             has 'loghandler' => (
101             is => 'ro',
102             isa => 'HashRef',
103             default => sub {{}},
104             );
105              
106             has 'log2handler' => (
107             is => 'ro',
108             isa => 'HashRef',
109             default => sub {{}},
110             );
111              
112             has 'setuphandler' => (
113             is => 'ro',
114             isa => 'HashRef',
115             default => sub {{}},
116             );
117              
118             has 'retries' => (
119             traits => ['Counter'],
120             is => 'ro',
121             isa => 'Num',
122             default => sub { 0 },
123             handles => {
124             inc_retry => 'inc',
125             dec_retry => 'dec',
126             reset_retries => 'reset',
127             },
128             );
129              
130             has '_requests' => (
131             is => 'ro',
132             isa => 'HashRef',
133             default => sub {{}},
134             init_arg => undef,
135             clearer => '_clear_requests',
136             );
137              
138             has '_connections' => (
139             is => 'ro',
140             isa => 'HashRef',
141             default => sub {{}},
142             init_arg => undef,
143             clearer => '_clear_connections',
144             );
145              
146             has '_chunkcount' => (
147             is => 'ro',
148             isa => 'HashRef',
149             default => sub {{}},
150             init_arg => undef,
151             clearer => '_clear_chunkcount',
152             );
153              
154             has '_responses' => (
155             is => 'ro',
156             isa => 'HashRef',
157             default => sub {{}},
158             init_arg => undef,
159             clearer => '_clear_responses',
160             );
161              
162             has '_factory' => (
163             is => 'ro',
164             isa => 'POE::Wheel::SocketFactory',
165             init_arg => undef,
166             clearer => '_clear_factory',
167             writer => '_set_factory',
168             );
169              
170             sub BUILDARGS {
171 6     6 1 17 my $class = shift;
172 6         43 my %args = @_;
173 6         79 $args{lc $_} = delete $args{$_} for keys %args;
174              
175 6 0 33     35 if ( $args{sslkeycert} and ref $args{sslkeycert} eq 'ARRAY'
      33        
176 0         0 and scalar @{ $args{sslkeycert} } == 2 ) {
177              
178 0         0 eval {
179 0         0 require POE::Component::SSLify;
180 0         0 import POE::Component::SSLify
181             qw( SSLify_Options SSLify_GetSocket Server_SSLify SSLify_GetCipher SSLify_GetCTX );
182 0         0 SSLify_Options( @{ $args{sslkeycert} } );
  0         0  
183             };
184 0 0       0 if ($@) {
185 0         0 warn "Unable to load PoCo::SSLify -> $@" if DEBUG;
186 0         0 delete $args{sslkeycert};
187             }
188             else {
189 0 0       0 if ( $args{sslintermediatecacert} ) {
190 0         0 my $ctx = SSLify_GetCTX();
191 0         0 Net::SSLeay::CTX_load_verify_locations($ctx, $args{sslintermediatecacert}, '');
192             }
193             }
194             }
195              
196 6         159 return $class->SUPER::BUILDARGS(%args);
197             }
198              
199             sub session_id {
200 0     0 0 0 shift->get_session_id;
201             }
202              
203             sub getsockname {
204 0     0 0 0 shift->_factory->getsockname;
205             }
206              
207             sub shutdown {
208 0     0 0 0 my $self = shift;
209 0         0 $poe_kernel->call( $self->get_session_id, 'SHUTDOWN', @_ );
210             }
211              
212             # This subroutine, when SimpleHTTP exits, will search for leaks
213             sub STOP {
214 6     6 0 1989 my $self = $_[OBJECT];
215             # Loop through all of the requests
216 6         14 foreach my $req ( keys %{ $self->_requests } ) {
  6         235  
217              
218             # Bite the programmer!
219             warn 'Did not get DONE/CLOSE event for Wheel ID '
220             . $req
221             . ' from IP '
222 0         0 . $self->_requests->{$req}->response->connection->remote_ip;
223             }
224              
225             # All done!
226 6         17 return 1;
227             }
228              
229             sub START {
230 6     6 0 3587 my ($kernel,$self) = @_[KERNEL,OBJECT];
231 6 50       281 $kernel->alias_set( $self->alias ) if $self->alias;
232 6 50       362 $kernel->refcount_increment( $self->get_session_id, __PACKAGE__ )
233             unless $self->alias;
234 6         176 MassageHandlers( $self->handlers );
235             # Start Listener
236 6         27 $kernel->yield( 'start_listener' );
237 6         229 return;
238             }
239              
240             # 'SHUTDOWN'
241             # Stops the server!
242             event 'SHUTDOWN' => sub {
243 6     6   17577 my ($kernel,$self,$graceful) = @_[KERNEL,OBJECT,ARG0];
244             # Shutdown the SocketFactory wheel
245 6 50       250 $self->_clear_factory if $self->_factory;
246              
247             # Debug stuff
248 6         27 warn 'Stopped listening for new connections!' if DEBUG;
249              
250             # Are we gracefully shutting down or not?
251 6 50       897 if ( $graceful ) {
252              
253             # Check for existing requests and keep-alive connections
254 0 0 0     0 if ( keys( %{ $self->_requests } ) == 0
  0         0  
255 0         0 and keys( %{ $self->_connections } ) == 0 )
256             {
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         215 foreach my $S ( $self->_requests, $self->_connections ) {
275 12         30 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         23 $kernel->alias_remove( $_ ) for $kernel->alias_list();
292 6 50       524 $kernel->refcount_decrement( $self->get_session_id, __PACKAGE__ )
293             unless $self->alias;
294              
295             # Debug stuff
296 6         12 warn 'Successfully stopped SimpleHTTP' if DEBUG;
297              
298             # Return success
299 6         17 return 1;
300             };
301              
302             # Sets up the SocketFactory wheel :)
303             event 'start_listener' => sub {
304 6     6   976 my ($kernel,$self,$noinc) = @_[KERNEL,OBJECT,ARG0];
305              
306 6         7 warn "Creating SocketFactory wheel now\n" if DEBUG;
307              
308             # Check if we should set up the wheel
309 6 50       215 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       218 $self->inc_retry unless $noinc;
317              
318             # Create our own SocketFactory Wheel :)
319 6 50       167 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         2345 my ( $port, $address ) =
328             sockaddr_in( $factory->getsockname );
329 6 50       320 $self->_set_port( $port ) if $self->port == 0;
330              
331 6         196 $self->_set_factory( $factory );
332              
333 6 50       178 if ( $self->setuphandler ) {
334 6         157 my $setuphandler = $self->setuphandler;
335 6 50 33     28 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     62 ) if $setuphandler->{'SESSION'} and $setuphandler->{'EVENT'};
345             }
346             }
347             }
348              
349 6         491 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 14 my $handler = shift;
407              
408             # Make sure it is ref to array
409 6 50 33     43 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         12 my $count = 0;
415 6         20 while ( $count < scalar(@$handler) ) {
416              
417             # Must be ref to hash
418 18 50 33     453 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         21 for keys %{ $handler->[$count] };
  18         177  
423              
424             # Make sure it got the 3 parts necessary
425 18 50 33     64 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     52 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     76 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       67 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         18 my $regex = undef;
451 18         16 eval { $regex = qr/$handler->[ $count ]->{'DIR'}/ };
  18         203  
452              
453             # Check for errors
454 18 50       31 if ($@) {
455 0         0 croak("HANDLER number $count has a malformed DIR -> $@");
456             }
457             else {
458              
459             # Store it!
460 18         25 $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         29 $count++;
469             }
470              
471             # Got here, success!
472 6         10 return 1;
473             }
474              
475             # 'Got_Connection'
476             # The actual manager of connections
477             event 'got_connection' => sub {
478 9     9   23525 my ($kernel,$self,$socket,$peeraddr,$peerport) = @_[KERNEL,OBJECT,ARG0..ARG2];
479              
480              
481             # Should we SSLify it?
482 9 50       350 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         81 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         2257 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         412 $self->_requests->{ $wheel->ID } =
514             POE::Component::Server::SimpleHTTP::State->new( wheel => $wheel );
515              
516             # Debug stuff
517 9         39 if (DEBUG) {
518             warn "Got_Connection completed creation of ReadWrite wheel ( "
519             . $wheel->ID . " )";
520             }
521              
522             # Success!
523 9         26 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   47493 my ($kernel,$self,$request,$id) = @_[KERNEL,OBJECT,ARG0,ARG1];
530 12         33 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       477 if ( $self->_connections->{$id} ) {
538 3         87 my $state = delete $self->_connections->{$id};
539 3         13 $state->reset;
540 3         87 $connection = $state->connection;
541 3         103 $state->clear_connection;
542 3         82 $self->_requests->{$id} = $state;
543 3         4 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       340 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       15 last SWITCH if $connection; # connection was kept-alive
  12         32  
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       263 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         269 $self->_requests->{$id}->wheel->get_input_handle()
571             );
572             }
573              
574             # The HTTP::Response object, the path
575 12         20 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       85 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       411 unless ( $self->proxymode ) {
602             # Add stuff it needs!
603 12         46 my $uri = $request->uri;
604 12         93 $uri->scheme('http');
605 12         15224 $uri->host( $self->hostname );
606 12         1314 $uri->port( $self->port );
607              
608             # Get the path
609 12         503 $path = $uri->path();
610 12 50 33     164 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         106 $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       6779 if keys( %{ $self->headers } ) != 0;
  12         352  
627             }
628              
629             # Check if the SimpleHTTP::Connection object croaked ( happens when sockets just disappear )
630 12 50       378 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       354 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       50 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         342 $self->_requests->{$id}->set_response( $response );
657 12         344 $self->_requests->{$id}->set_request( $request );
658 12         397 $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     343 if ( $self->loghandler and scalar keys %{ $self->loghandler } == 2 ) {
  12         322  
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       37 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         16 foreach my $handler ( @{ $self->handlers } ) {
  12         337  
700              
701             # Check if this matches
702 32 100       131 if ( $path =~ $handler->{'RE'} ) {
703              
704             # Send this off!
705             $kernel->post( $handler->{'SESSION'}, $handler->{'EVENT'}, $request,
706 9         47 $response, $handler->{'DIR'}, );
707              
708             # Make sure we croak if we have an issue posting
709 9 50       753 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         29 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         22 $response->code(404);
722 3         28 $response->content('404 Not Found');
723 3         56 $kernel->yield( 'DONE', $response );
724 3         153 return;
725             };
726              
727             # 'Got_Flush'
728             # Finished with a request!
729             event 'got_flush' => sub {
730 12     12   2738 my ($kernel,$self,$id) = @_[KERNEL,OBJECT,ARG0];
731              
732 12 50       423 return unless defined $self->_requests->{$id};
733              
734             # Debug stuff
735 12         11 warn "Got Flush event for wheel ID ( $id )" if DEBUG;
736              
737 12 50       329 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       316 if ( $self->_requests->{$id}->done ) {
745              
746 12 100       38 if ( $self->must_keepalive( $id ) ) {
747 3         23 warn "Keep-alive id=$id ..." if DEBUG;
748 3         90 my $state = delete $self->_requests->{$id};
749 3         86 $state->set_connection( $state->response->connection );
750 3         11 $state->reset;
751 3         81 $self->_connections->{$id} = $state;
752 3         84 delete $self->_chunkcount->{$id};
753 3         81 delete $self->_responses->{$id};
754             }
755             else {
756             # Shutdown read/write on the wheel
757 9         251 $self->_requests->{$id}->close_wheel;
758 9         277 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       393 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         30 return 1;
784             };
785              
786             # should we keep-alive the connection?
787             sub must_keepalive {
788 12     12 0 24 my ( $self, $id ) = @_;
789              
790 12 100       336 return unless $self->keepalive;
791              
792 4         122 my $resp = $self->_requests->{$id}->response;
793 4         105 my $req = $self->_requests->{$id}->request;
794              
795             # error = close
796 4 100       16 return 0 if $resp->is_error;
797              
798             # Connection is a comma-seperated header
799 3   50     32 my $conn = lc ($req->header('Connection') || '');
800 3 50       97 return 0 if ",$conn," =~ /,\s*close\s*,/;
801 3   50     8 $conn = lc ($req->header('Proxy-Connection') || '');
802 3 50       107 return 0 if ",$conn," =~ /,\s*close\s*,/;
803 3   50     8 $conn = lc ($resp->header('Connection') || '');
804 3 50       68 return 0 if ",$conn," =~ /,\s*close\s*,/;
805              
806             # HTTP/1.1 = keep
807 3 50       6 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   3920 my ($kernel,$self,$response) = @_[KERNEL,OBJECT,ARG0];
858              
859             # Check if we got it
860 12 50 33     88 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         476 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       354 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       316 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       313 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       316 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         48 $self->fix_headers( $response );
920              
921             # Send it out!
922 12         468 $self->_requests->{$id}->wheel->put($response);
923              
924             # Mark this socket done
925 12         2971 $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     387 if ( $self->log2handler and scalar keys %{ $self->log2handler } == 2 ) {
  12         364  
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         14 warn "Completed with Wheel ID $id" if DEBUG;
948              
949             # Success!
950 12         35 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 16 my ( $self, $response, $stream ) = @_;
1064              
1065             # Set the date if needed
1066 12 50       78 if ( !$response->header('Date') ) {
1067 12         544 $response->header( 'Date', time2str(time) );
1068             }
1069              
1070             # Set the Content-Length if needed
1071 12 50 33     930 if ( !$stream and !$self->proxymode
      33        
1072             and !defined $response->header('Content-Length')
1073             and my $len = length $response->content )
1074             {
1075 6     6   28733 use bytes;
  6         260  
  6         49  
1076 12         439 $response->header( 'Content-Length', $len );
1077             }
1078              
1079             # Set the Content-Type if needed
1080 12 100       337 if ( !$response->header('Content-Type') ) {
1081 5         121 $response->header( 'Content-Type', 'text/plain' );
1082             }
1083              
1084 12 50       318 if ( !$response->protocol ) {
1085 12         444 my $request = $self->_requests->{ $response->_WHEEL }->request;
1086 12 50 33     113 return unless $request and $request->isa('HTTP::Request');
1087 12 50       48 unless ( $request->method eq 'HEAD' ) {
1088 12         131 $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   74 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       6 unless ( ref $connection ) {
1147 2         4 my $id = $connection;
1148 2 50 33     60 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         53 $connection = $self->_requests->{$id}->response->connection;
1155             }
1156 2 50       9 unless ( ref $connection ) {
1157 0         0 die "Can't find connection object for request $id";
1158             }
1159             }
1160              
1161 2 50       5 if ($state) {
1162 2         8 $connection->_on_close( $sender->ID, $state, @params );
1163             }
1164             else {
1165 0           $connection->_on_close($sender->ID);
1166             }
1167             };
1168              
1169 6     6   2396 no MooseX::POE;
  6         9  
  6         31  
1170              
1171             __PACKAGE__->meta->make_immutable( );
1172              
1173             "Simple In'it";
1174              
1175             __END__
1176              
1177             =pod
1178              
1179             =encoding UTF-8
1180              
1181             =head1 NAME
1182              
1183             POE::Component::Server::SimpleHTTP - Perl extension to serve HTTP requests in POE.
1184              
1185             =head1 VERSION
1186              
1187             version 2.26
1188              
1189             =head1 SYNOPSIS
1190              
1191             use POE;
1192             use POE::Component::Server::SimpleHTTP;
1193              
1194             # Start the server!
1195             POE::Component::Server::SimpleHTTP->new(
1196             'ALIAS' => 'HTTPD',
1197             'PORT' => 11111,
1198             'HOSTNAME' => 'MySite.com',
1199             'HANDLERS' => [
1200             {
1201             'DIR' => '^/bar/.*',
1202             'SESSION' => 'HTTP_GET',
1203             'EVENT' => 'GOT_BAR',
1204             },
1205             {
1206             'DIR' => '^/$',
1207             'SESSION' => 'HTTP_GET',
1208             'EVENT' => 'GOT_MAIN',
1209             },
1210             {
1211             'DIR' => '^/foo/.*',
1212             'SESSION' => 'HTTP_GET',
1213             'EVENT' => 'GOT_NULL',
1214             },
1215             {
1216             'DIR' => '.*',
1217             'SESSION' => 'HTTP_GET',
1218             'EVENT' => 'GOT_ERROR',
1219             },
1220             ],
1221              
1222             'LOGHANDLER' => { 'SESSION' => 'HTTP_GET',
1223             'EVENT' => 'GOT_LOG',
1224             },
1225              
1226             'LOG2HANDLER' => { 'SESSION' => 'HTTP_GET',
1227             'EVENT' => 'POSTLOG',
1228             },
1229              
1230             # In the testing phase...
1231             'SSLKEYCERT' => [ 'private-key.pem', 'public-cert.pem' ],
1232             'SSLINTERMEDIATECACERT' => 'intermediate-ca-cert.pem',
1233             ) or die 'Unable to create the HTTP Server';
1234              
1235             # Create our own session to receive events from SimpleHTTP
1236             POE::Session->create(
1237             inline_states => {
1238             '_start' => sub { $_[KERNEL]->alias_set( 'HTTP_GET' );
1239             $_[KERNEL]->post( 'HTTPD', 'GETHANDLERS', $_[SESSION], 'GOT_HANDLERS' );
1240             },
1241              
1242             'GOT_BAR' => \&GOT_REQ,
1243             'GOT_MAIN' => \&GOT_REQ,
1244             'GOT_ERROR' => \&GOT_ERR,
1245             'GOT_NULL' => \&GOT_NULL,
1246             'GOT_HANDLERS' => \&GOT_HANDLERS,
1247             'GOT_LOG' => \&GOT_LOG,
1248             },
1249             );
1250              
1251             # Start POE!
1252             POE::Kernel->run();
1253              
1254             sub GOT_HANDLERS {
1255             # ARG0 = HANDLERS array
1256             my $handlers = $_[ ARG0 ];
1257              
1258             # Move the first handler to the last one
1259             push( @$handlers, shift( @$handlers ) );
1260              
1261             # Send it off!
1262             $_[KERNEL]->post( 'HTTPD', 'SETHANDLERS', $handlers );
1263             }
1264              
1265             sub GOT_NULL {
1266             # ARG0 = HTTP::Request object, ARG1 = HTTP::Response object, ARG2 = the DIR that matched
1267             my( $request, $response, $dirmatch ) = @_[ ARG0 .. ARG2 ];
1268              
1269             # Kill this!
1270             $_[KERNEL]->post( 'HTTPD', 'CLOSE', $response );
1271             }
1272              
1273             sub GOT_REQ {
1274             # ARG0 = HTTP::Request object, ARG1 = HTTP::Response object, ARG2 = the DIR that matched
1275             my( $request, $response, $dirmatch ) = @_[ ARG0 .. ARG2 ];
1276              
1277             # Do our stuff to HTTP::Response
1278             $response->code( 200 );
1279             $response->content( 'Some funky HTML here' );
1280              
1281             # We are done!
1282             # For speed, you could use $_[KERNEL]->call( ... )
1283             $_[KERNEL]->post( 'HTTPD', 'DONE', $response );
1284             }
1285              
1286             sub GOT_ERR {
1287             # ARG0 = HTTP::Request object, ARG1 = HTTP::Response object, ARG2 = the DIR that matched
1288             my( $request, $response, $dirmatch ) = @_[ ARG0 .. ARG2 ];
1289              
1290             # Check for errors
1291             if ( ! defined $request ) {
1292             $_[KERNEL]->post( 'HTTPD', 'DONE', $response );
1293             return;
1294             }
1295              
1296             # Do our stuff to HTTP::Response
1297             $response->code( 404 );
1298             $response->content( "Hi visitor from " . $response->connection->remote_ip . ", Page not found -> '" . $request->uri->path . "'" );
1299              
1300             # We are done!
1301             # For speed, you could use $_[KERNEL]->call( ... )
1302             $_[KERNEL]->post( 'HTTPD', 'DONE', $response );
1303             }
1304              
1305             sub GOT_LOG {
1306             # ARG0 = HTTP::Request object, ARG1 = remote IP
1307             my ($request, $remote_ip) = @_[ARG0,ARG1];
1308              
1309             # Do some sort of logging activity.
1310             # If the request was malformed, $request = undef
1311             # CHECK FOR A REQUEST OBJECT BEFORE USING IT.
1312             if( $request ) {
1313             {
1314             warn join(' ', time(), $remote_ip, $request->uri ), "\n";
1315             } else {
1316             warn join(' ', time(), $remote_ip, 'Bad request' ), "\n";
1317             }
1318              
1319             return;
1320             }
1321              
1322             =head1 DESCRIPTION
1323              
1324             This module makes serving up HTTP requests a breeze in POE.
1325              
1326             The hardest thing to understand in this module is the HANDLERS. That's it!
1327              
1328             The standard way to use this module is to do this:
1329              
1330             use POE;
1331             use POE::Component::Server::SimpleHTTP;
1332              
1333             POE::Component::Server::SimpleHTTP->new( ... );
1334              
1335             POE::Session->create( ... );
1336              
1337             POE::Kernel->run();
1338              
1339             =head2 Starting SimpleHTTP
1340              
1341             To start SimpleHTTP, just call it's new method:
1342              
1343             POE::Component::Server::SimpleHTTP->new(
1344             'ALIAS' => 'HTTPD',
1345             'ADDRESS' => '192.168.1.1',
1346             'PORT' => 11111,
1347             'HOSTNAME' => 'MySite.com',
1348             'HEADERS' => {},
1349             'HANDLERS' => [ ],
1350             );
1351              
1352             This method will die on error or return success.
1353              
1354             This constructor accepts only 7 options.
1355              
1356             =over 4
1357              
1358             =item C<ALIAS>
1359              
1360             This will set the alias SimpleHTTP uses in the POE Kernel.
1361             This will default to "SimpleHTTP"
1362              
1363             =item C<ADDRESS>
1364              
1365             This value will be passed to POE::Wheel::SocketFactory to bind to, will use INADDR_ANY if it is nothing is provided.
1366              
1367             =item C<PORT>
1368              
1369             This value will be passed to POE::Wheel::SocketFactory to bind to.
1370              
1371             =item C<HOSTNAME>
1372              
1373             This value is for the HTTP::Request's URI to point to.
1374             If this is not supplied, SimpleHTTP will use Sys::Hostname to find it.
1375              
1376             =item C<HEADERS>
1377              
1378             This should be a hashref, that will become the default headers on all HTTP::Response objects.
1379             You can override this in individual requests by setting it via $request->header( ... )
1380              
1381             For more information, consult the L<HTTP::Headers> module.
1382              
1383             =item C<HANDLERS>
1384              
1385             This is the hardest part of SimpleHTTP :)
1386              
1387             You supply an array, with each element being a hash. All the hashes should contain those 3 keys:
1388              
1389             DIR -> The regexp that will be used, more later.
1390              
1391             SESSION -> The session to send the input
1392              
1393             EVENT -> The event to trigger
1394              
1395             The DIR key should be a valid regexp. This will be matched against the current request path.
1396             Pseudocode is: if ( $path =~ /$DIR/ )
1397              
1398             NOTE: The path is UNIX style, not MSWIN style ( /blah/foo not \blah\foo )
1399              
1400             Now, if you supply 100 handlers, how will SimpleHTTP know what to do? Simple! By passing in an array in the first place,
1401             you have already told SimpleHTTP the order of your handlers. They will be tried in order, and if a match is not found,
1402             SimpleHTTP will return a 404 response.
1403              
1404             This allows some cool things like specifying 3 handlers with DIR of:
1405             '^/foo/.*', '^/$', '.*'
1406              
1407             Now, if the request is not in /foo or not root, your 3rd handler will catch it, becoming the "404 not found" handler!
1408              
1409             NOTE: You might get weird Session/Events, make sure your handlers are in order, for example: '^/', '^/foo/.*'
1410             The 2nd handler will NEVER get any requests, as the first one will match ( no $ in the regex )
1411              
1412             Now, here's what a handler receives:
1413              
1414             ARG0 -> HTTP::Request object
1415              
1416             ARG1 -> POE::Component::Server::SimpleHTTP::Response object
1417              
1418             ARG2 -> The exact DIR that matched, so you can see what triggered what
1419              
1420             NOTE: If ARG0 is undef, that means POE::Filter::HTTPD encountered an error parsing the client request, simply modify the HTTP::Response
1421             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
1422             is a "catch-all" DIR regex like '.*', it will catch the errors, and only that one.
1423              
1424             NOTE: The only way SimpleHTTP will leak memory ( hopefully heh ) is if you discard the SimpleHTTP::Response object without sending it
1425             back to SimpleHTTP via the DONE/CLOSE events, so never do that!
1426              
1427             =item C<KEEPALIVE>
1428              
1429             Set to true to enable HTTP keep-alive support. Connections will be
1430             kept alive until the client closes the connection. All HTTP/1.1 connections
1431             are kept-open, unless you set the response C<Connection> header to C<close>.
1432              
1433             $response->header( Connection => 'close' );
1434              
1435             If you want more control, use L<POE::Component::Server::HTTP::KeepAlive>.
1436              
1437             =item C<LOGHANDLER>
1438              
1439             Expects a hashref with the following key, values:
1440              
1441             SESSION -> The session to send the input
1442              
1443             EVENT -> The event to trigger
1444              
1445             You will receive an event for each request to the server from clients. Malformed client requests will not be passed into the handler. Instead
1446             undef will be passed.
1447             Event is called before ANY content handler is called.
1448              
1449             The event will have the following parameters:
1450              
1451             ARG0 -> HTTP::Request object/undef if client request was malformed.
1452              
1453             ARG1 -> the IP address of the client
1454              
1455             =item C<LOG2HANDLER>
1456              
1457             Expect a hashref with the following key, values:
1458              
1459             SESSION -> The session to send the input
1460              
1461             EVENT -> The event to trigger
1462              
1463             You will receive an event for each response that hit DONE call. Malformed client requests will not be passed into the handler.
1464             Event is after processing all content handlers.
1465              
1466             The event will have the following parameters:
1467              
1468             ARG0 -> HTTP::Request object
1469              
1470             ARG1 -> HTTP::Response object
1471              
1472             That makes possible following code:
1473              
1474             my ($login, $password) = $request->authorization_basic();
1475             printf STDERR "%s - %s [%s] \"%s %s %s\" %d %d\n",
1476             $response->connection->remote_ip, $login||'-', POSIX::strftime("%d/%b/%Y:%T %z",localtime(time())),
1477             $request->method(), $request->uri()->path(), $request->protocol(),
1478             $response->code(), length($response->content());
1479              
1480             Emulate apache-like logs for PoCo::Server::SimpleHTTP
1481              
1482             =item C<SETUPHANDLER>
1483              
1484             Expects a hashref with the following key, values:
1485              
1486             SESSION -> The session to send the input
1487              
1488             EVENT -> The event to trigger
1489              
1490             You will receive an event when the listener wheel has been setup.
1491              
1492             Currently there are no parameters returned.
1493              
1494             =item C<SSLKEYCERT>
1495              
1496             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
1497             is greatly welcome!
1498              
1499             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!
1500              
1501             =item C<SSLINTERMEDIATECACERT>
1502              
1503             This option is needed in case the SSL certificate references an intermediate certification authority certificate.
1504              
1505             =item C<PROXYMODE>
1506              
1507             Set this to a true value to enable the server to act as a proxy server, ie. it won't mangle the HTTP::Request
1508             URI.
1509              
1510             =back
1511              
1512             =head2 Events
1513              
1514             SimpleHTTP is so simple, there are only 8 events available.
1515              
1516             =over 4
1517              
1518             =item C<DONE>
1519              
1520             This event accepts only one argument: the HTTP::Response object we sent to the handler.
1521              
1522             Calling this event implies that this particular request is done, and will proceed to close the socket.
1523              
1524             NOTE: This method automatically sets those 3 headers if they are not already set:
1525             Date -> Current date stringified via HTTP::Date->time2str
1526             Content-Type -> text/html
1527             Content-Length -> length( $response->content )
1528              
1529             To get greater throughput and response time, do not post() to the DONE event, call() it!
1530             However, this will force your program to block while servicing web requests...
1531              
1532             =item C<CLOSE>
1533              
1534             This event accepts only one argument: the HTTP::Response object we sent to the handler.
1535              
1536             Calling this event will close the socket, not sending any output
1537              
1538             =item C<GETHANDLERS>
1539              
1540             This event accepts 2 arguments: The session + event to send the response to
1541              
1542             This event will send back the current HANDLERS array ( deep-cloned via Storable::dclone )
1543              
1544             The resulting array can be played around to your tastes, then once you are done...
1545              
1546             =item C<SETHANDLERS>
1547              
1548             This event accepts only one argument: pointer to HANDLERS array
1549              
1550             BEWARE: if there is an error in the HANDLERS, SimpleHTTP will die!
1551              
1552             =item C<SETCLOSEHANDLER>
1553              
1554             $_[KERNEL]->call( $_[SENDER], 'SETCLOSEHANDLER', $connection,
1555             $event, @args );
1556              
1557             Calls C<$event> in the current session when C<$connection> is closed. You
1558             could use for persistent connection handling.
1559              
1560             Multiple session may register close handlers.
1561              
1562             Calling SETCLOSEHANDLER without C<$event> to remove the current session's
1563             handler:
1564              
1565             $_[KERNEL]->call( $_[SENDER], 'SETCLOSEHANDLER', $connection );
1566              
1567             You B<must> make sure that C<@args> doesn't cause a circular
1568             reference. Ideally, use C<$connection->ID> or some other unique value
1569             associated with this C<$connection>.
1570              
1571             =item C<STARTLISTEN>
1572              
1573             Starts the listening socket, if it was shut down
1574              
1575             =item C<STOPLISTEN>
1576              
1577             Simply a wrapper for SHUTDOWN GRACEFUL, but will not shutdown SimpleHTTP if there is no more requests
1578              
1579             =item C<SHUTDOWN>
1580              
1581             Without arguments, SimpleHTTP does this:
1582             Close the listening socket
1583             Kills all pending requests by closing their sockets
1584             Removes it's alias
1585              
1586             With an argument of 'GRACEFUL', SimpleHTTP does this:
1587             Close the listening socket
1588             Waits for all pending requests to come in via DONE/CLOSE, then removes it's alias
1589              
1590             =item C<STREAM>
1591              
1592             With a $response argument it streams the content and calls back the streaming event
1593             of the user's session (or with the dont_flush option you're responsible for calling
1594             back your session's streaming event).
1595              
1596             To use the streaming feature see below.
1597              
1598             =back
1599              
1600             =head2 Streaming with SimpleHTTP
1601              
1602             It's possible to send data as a stream to clients (unbuffered and integrated in the
1603             POE loop).
1604              
1605             Just create your session to receive events from SimpleHTTP as usually and add a
1606             streaming event, this event will be triggered over and over each time you set the
1607             $response to a streaming state and once you trigger it:
1608              
1609             # sets the response as streamed within our session which alias is HTTP_GET
1610             # with the event GOT_STREAM
1611             $response->stream(
1612             session => 'HTTP_GET',
1613             event => 'GOT_STREAM',
1614             dont_flush => 1
1615             );
1616              
1617             # then you can simply yield your streaming event, once the GOT_STREAM event
1618             # has reached its end it will be triggered again and again, until you
1619             # send a CLOSE event to the kernel with the appropriate response as parameter
1620             $kernel->yield('GOT_STREAM', $response);
1621              
1622             The optional dont_flush option gives the user the ability to control the callback
1623             to the streaming event, which means once your stream event has reached its end
1624             it won't be called, you have to call it back.
1625              
1626             You can now send data by chunks and either call yourself back (via POE) or
1627             shutdown when your streaming is done (EOF for example).
1628              
1629             sub GOT_STREAM {
1630             my ( $kernel, $heap, $response ) = @_[KERNEL, HEAP, ARG0];
1631              
1632             # sets the content of the response
1633             $response->content("Hello World\n");
1634              
1635             # send it to the client
1636             POE::Kernel->post('HTTPD', 'STREAM', $response);
1637              
1638             # if we have previously set the dont_flush option
1639             # we have to trigger our event back until the end of
1640             # the stream like this (that can be a yield, of course):
1641             #
1642             # $kernel->delay('GOT_STREAM', 1, $stream );
1643              
1644             # otherwise the GOT_STREAM event is triggered continuously until
1645             # we call the CLOSE event on the response like that :
1646             #
1647             if ($heap{'streaming_is_done'}) {
1648             # close the socket and end the stream
1649             POE::Kernel->post('HTTPD', 'CLOSE', $response );
1650             }
1651             }
1652              
1653             The dont_flush option is there to be able to control the frequency of flushes
1654             to the client.
1655              
1656             =head2 SimpleHTTP Notes
1657              
1658             You can enable debugging mode by doing this:
1659              
1660             sub POE::Component::Server::SimpleHTTP::DEBUG () { 1 }
1661             use POE::Component::Server::SimpleHTTP;
1662              
1663             Also, this module will try to keep the Listening socket alive.
1664             if it dies, it will open it again for a max of 5 retries.
1665              
1666             You can override this behavior by doing this:
1667              
1668             sub POE::Component::Server::SimpleHTTP::MAX_RETRIES () { 10 }
1669             use POE::Component::Server::SimpleHTTP;
1670              
1671             For those who are pondering about basic-authentication, here's a tiny snippet to put in the Event handler
1672              
1673             # Contributed by Rocco Caputo
1674             sub Got_Request {
1675             # ARG0 = HTTP::Request, ARG1 = HTTP::Response
1676             my( $request, $response ) = @_[ ARG0, ARG1 ];
1677              
1678             # Get the login
1679             my ( $login, $password ) = $request->authorization_basic();
1680              
1681             # Decide what to do
1682             if ( ! defined $login or ! defined $password ) {
1683             # Set the authorization
1684             $response->header( 'WWW-Authenticate' => 'Basic realm="MyRealm"' );
1685             $response->code( 401 );
1686             $response->content( 'FORBIDDEN.' );
1687              
1688             # Send it off!
1689             $_[KERNEL]->post( 'SimpleHTTP', 'DONE', $response );
1690             } else {
1691             # Authenticate the user and move on
1692             }
1693             }
1694              
1695             =head2 EXPORT
1696              
1697             Nothing.
1698              
1699             =for Pod::Coverage MassageHandlers
1700             START
1701             STOP
1702             fix_headers
1703             getsockname
1704             must_keepalive
1705             session_id
1706             shutdown
1707              
1708             =head1 ABSTRACT
1709              
1710             An easy to use HTTP daemon for POE-enabled programs
1711              
1712             =head1 SEE ALSO
1713              
1714             L<POE>
1715              
1716             L<POE::Filter::HTTPD>
1717              
1718             L<HTTP::Request>
1719              
1720             L<HTTP::Response>
1721              
1722             L<POE::Component::Server::SimpleHTTP::Connection>
1723              
1724             L<POE::Component::Server::SimpleHTTP::Response>
1725              
1726             L<POE::Component::Server::SimpleHTTP::PreFork>
1727              
1728             L<POE::Component::SSLify>
1729              
1730             =head1 AUTHOR
1731              
1732             Apocalypse <APOCAL@cpan.org>
1733              
1734             =head1 COPYRIGHT AND LICENSE
1735              
1736             This software is copyright (c) 2017 by Apocalypse, Chris Williams, Eriam Schaffter, Marlon Bailey and Philip Gwyn.
1737              
1738             This is free software; you can redistribute it and/or modify it under
1739             the same terms as the Perl 5 programming language system itself.
1740              
1741             =cut