HTTP::Daemon::Threaded::Listener


HTTP-Daemon-Threaded documentation Contained in the HTTP-Daemon-Threaded distribution.

Index


Code Index:


HTTP-Daemon-Threaded documentation Contained in the HTTP-Daemon-Threaded distribution.
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;

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;
}

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};
}

sub get_simplex_methods {
	return {
		close => 1,
		setLogLevel => 1,
		setListenInterval => 1,
	};
}

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;
}

sub getSocket { return shift->{_fd}; }
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() ?
#
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;
}

sub setLogLevel {
	my ($self, $level) = @_;

	$_->setLogLevel($level)
		foreach (@{$self->{_clients}});
	$self->{LogLevel} = $level;
	return 1;
}
sub setListenInterval {
	$_[0]->{_sktsel}->setTimeout($_[1]);
}
sub getListenInterval {
	return $_[0]->{_sktsel}->getTimeout();
}

1;