=pod =begin classdoc Web network listener: accepts network connections, and routes them to WebClient objects to handle the web requests. Creates and maintains a pool of WebClient objects.

A threads::shared array is shared between this object and the WebClient objects so that WebClients can return themselves to the free list quickly; a future release may convert the freelist to a full object, in the event the allocate/free methods require a more complex process.

Copyright© 2006-2008, Dean Arnold, Presicient Corp., USA
All rights reserved.

Licensed under the Academic Free License version 3.0, as specified in the at OpenSource.org. @author D. Arnold @since 2006-08-21 @self $self =end classdoc =cut package HTTP::Daemon::Threaded::Listener; use Socket; use Sys::Hostname; use threads; use threads::shared; use HTTP::Daemon::Threaded::Socket; use HTTP::Daemon::Threaded::WebClient; use HTTP::Daemon::Threaded::IOSelector; use HTTP::Daemon::Threaded::Logable; use Thread::Apartment::MuxServer; use base qw(HTTP::Daemon::Threaded::Logable Thread::Apartment::MuxServer); use strict; use warnings; our $VERSION = '0.91'; use constant HTTPD_INTERVAL => 0.5; =pod =begin classdoc Constructor. Opens the HTTPD listener socket and creates its selector. Creates a pool of HTTP::Daemon::Threaded::WebClient apartment threaded objects, based on the specified MaxClient parameter.

Note that the following parameters are recognized by HTTP::Daemon::Threaded and/or HTTP::Daemon::Threaded::WebClient, but applications may supply additional parameter key/value pairs which will be provided to the constructor for any specified HTTP::Daemon::Threaded::ContentParams class. @param AptTimeout (optional) Thread::Apartment proxy return timeout @param Port (optional) TCP listen port; default 80. @param MaxClients (optional) max number of client handlers to spawn; default 5 @param LogLevel (optional) logging level; 1 => errors only; 2 => errors and warnings only; 3 => errors, warnings, and info messages; default 1 @param EventLogger (optional) Instance of a HTTP::Daemon::Threaded::Logger to receive event notifications (except for web requests) @param WebLogger (optional) Instance of a HTTP::Daemon::Threaded::Logger to receive web request notifications @param Handlers (required) URI handler map; arrayref mapping URI regex's to handler package names @param UserAuth (optional) instance of a subclass of HTTP::Daemon::Threaded::Auth (not yet supported) @param SessionCache (optional) instance of a subclass of HTTP::Daemon::Threaded::SessionCache to be used to create/manage sessions @param InactivityTimer (optional) number of seconds a WebClient waits before disconnecting an idle connection; default 10 minutes @param ContentParams (optional) name of concrete implementation of HTTP::Daemon::Threaded::ContentParams @param DocRoot (optional) root directory for default file based content handler @param ProductTokens (optional) product token string to return to client; default is 'HTTP::Daemon::Threaded/' @param MediaTypes (optional) hashref mapping 'Content-Type' specifications to file qualifier strings. Values may be either a single string literal, or @param SelectInterval (optional) seconds to wait in select()'s on sockets. May be fractional; default 0.5 @return HTTP::Daemon::Threaded object =end classdoc =cut sub new { my ($class, %args) = @_; $args{SelectInterval} = HTTPD_INTERVAL unless $args{SelectInterval}; my $self = { %args, _status => 'starting' }; bless $self, $class; # # setup our proxy version # $self->set_client(delete $self->{AptTAC}); # # open our listener # $self->{Port} = $args{Port} = 80 unless exists $args{Port}; $self->{_fd} = HTTP::Daemon::Threaded::Socket->new( LocalPort => $args{Port}, Proto => 'tcp', Listen => 10); $self->logError("Cannot get listener for Web Server: $!"), $@ = "Cannot get listener for Web Server: $!", return undef unless $self->{_fd}; # # register ourselves with it # (for single threaded mode) # $self->{_fd}->setContext($self); $self->logInfo("Created listener\n"); # # create a selector # $self->{_sktsel} = HTTP::Daemon::Threaded::IOSelector->new($args{SelectInterval}); $self->{_sktsel}->addNoWrite($self->{_fd}); # # create request handler pool # my @avail_clients : shared = (); $self->{_avail_clients} = \@avail_clients; my @webclients = (undef); $self->{_clients} = \@webclients; # # update the args # $self->{LogLevel} = $args{LogLevel} = 1 unless exists $args{LogLevel}; delete $args{MaxClients}; # # normalize docroot if needed # $args{DocRoot} .= '/' if (defined $args{DocRoot}) && (substr($args{DocRoot}, -1, 1) ne '/'); my $url = "http://"; my $addr = $self->{_fd}->sockaddr; $url .= (!$addr || $addr eq INADDR_ANY) ? lc Sys::Hostname::hostname() : (gethostbyaddr($addr, AF_INET) || inet_ntoa($addr)); my $port = $self->{_fd}->sockport; $url .= ":$args{Port}" unless ($args{Port} == 80); $args{URL} = $url . '/'; delete $args{Port}; $args{ProductTokens} = "HTTP::Daemon::Threaded/$VERSION" unless $args{ProductTokens}; $args{HTTPD} = $self->get_client(); $args{AptClass} = 'HTTP::Daemon::Threaded::WebClient'; $args{FreeList} = \@avail_clients; # # note that this inactivity timer may be overridden by # any Session object's timeout # $self->{InactivityTimer} = $args{InactivityTimer} = 10 * 60 unless exists $args{InactivityTimer}; foreach (1..$self->{MaxClients}) { $args{ID} = $_; push @webclients, Thread::Apartment->new(%args); pop @webclients, $@ = 'Unable to create a WebClient instance.', $self->logWarning('Unable to create a WebClient instance.'), return undef unless $webclients[-1]; push @{$self->{_avail_clients}}, $_; $self->logInfo("Created WebClient\n"); } $self->{_status} = 'running'; return $self; } =pod =begin classdoc Thread::Apartment::MuxServer::run() implementation. @return 1 =end classdoc =cut sub run { my $self = shift; while (1) { if ($self->{_sktsel}) { # # HTTP::Daemon::Threaded::IOSelector does the heavy lifting # my $elapsed = $self->{_sktsel}->select(); print STDERR "Long select!!! $elapsed\n" if ($elapsed >= 1); } else { select(undef, undef, undef, 0.1); } return undef unless $self->handle_method_requests(); # # shutdown may remove our selector # # last unless $self->{_sktsel}; } return undef; } sub status { #print "Returning status ", $_[0]->{_status}, "\n"; return $_[0]->{_status}; } =pod =begin classdoc Overrides Thread::Apartment::Server::get_simplex_methods() @return hashref of simplex method names =end classdoc =cut sub get_simplex_methods { return { close => 1, setLogLevel => 1, setListenInterval => 1, }; } =pod =begin classdoc Closes the listen socket and stops all the WebClient threads. @simplex @return 1 =end classdoc =cut sub close { my $self = shift; $self->logInfo("shutdown requested\n"); delete $self->{_sktsel}; $self->{_fd}->close(), delete $self->{_fd} if $self->{_fd}; # # queue stops first, then join # if ($self->{_clients}) { map { $_->stop() if $_; } @{$self->{_clients}}; map { $_->join() if $_; } @{$self->{_clients}}; } $self->{_status} = 'stopped'; return 1; } =pod =begin classdoc Return the listener socket. @return HTTP::Daemon::Threaded::Socket listen socket object =end classdoc =cut sub getSocket { return shift->{_fd}; } =pod =begin classdoc Handle listen socket events. Accepts a new connection request, allocates a WebClient to handle it, and passes the new socket to the WebClient @return 1 =end classdoc =cut sub handleSocketEvent { my $self = shift; $self->logInfo('Got web connection request.'); # # client must accept(): must make this duplex, so we don't keep # pinging the listener # my $skt = $self->{_fd}->accept(); return 1 unless $skt; my $client = $self->_get_client(); $skt->close(), return 1 unless $client; # # this waits for client... # $client->acceptConnection($skt->fileno()); return 1; } # # do we need to close the file here ? # maybe we should just let the clients accept() ? # =pod =begin classdoc Handle socket errors. @deprecated @return undef =end classdoc =cut sub handleSocketError { my $self = shift; $self->logWarn("Problem with web listener."); warn "Problem with web listener, exitting...\n"; return undef; } sub _get_client { my $self = shift; my $client; { lock(@{$self->{_avail_clients}}); $client = pop @{$self->{_avail_clients}}; } return $client ? $self->{_clients}[$client] : undef; } =pod =begin classdoc Set log level. Called from WebClient when loglevel update is requested. @simplex @param $level new log level @return 1 =end classdoc =cut sub setLogLevel { my ($self, $level) = @_; $_->setLogLevel($level) foreach (@{$self->{_clients}}); $self->{LogLevel} = $level; return 1; } =pod =begin classdoc Set IO::Select() interval. Called when HTTP::Daemon::Threaded select interval is updated. @simplex @param $interval new interval; fractional number of seconds @return 1 =end classdoc =cut sub setListenInterval { $_[0]->{_sktsel}->setTimeout($_[1]); } =pod =begin classdoc Get IO::Select() interval. Called when HTTP::Daemon::Threaded config info is requested. @return current listener interval =end classdoc =cut sub getListenInterval { return $_[0]->{_sktsel}->getTimeout(); } 1;