FusionInventory-Agent
view release on metacpan or search on metacpan
lib/FusionInventory/Agent/HTTP/Server.pm view on Meta::CPAN
package FusionInventory::Agent::HTTP::Server;
use strict;
use warnings;
use UNIVERSAL::require;
use English qw(-no_match_vars);
use File::Basename;
use HTTP::Daemon;
use IO::Handle;
use Net::IP;
use Text::Template;
use File::Glob;
use URI;
use Socket qw(IN6ADDR_ANY inet_ntop);
use FusionInventory::Agent::Version;
use FusionInventory::Agent::Logger;
use FusionInventory::Agent::Tools;
use FusionInventory::Agent::Tools::Network;
# Limit maximum requests number handled in a keep-alive connection
use constant MaxKeepAlive => 8;
my $log_prefix = "[http server] ";
sub new {
my ($class, %params) = @_;
my $self = {
logger => $params{logger} ||
FusionInventory::Agent::Logger->new(),
agent => $params{agent},
htmldir => $params{htmldir},
ip => $params{ip},
port => $params{port} || 62354,
listeners => {},
};
bless $self, $class;
$self->setTrustedAddresses(%params);
# Load any Server sub-module as plugin
my @plugins = ();
my ($sub_modules_path) = $INC{module2file(__PACKAGE__)} =~ /(.*)\.pm/;
foreach my $file (File::Glob::bsd_glob("$sub_modules_path/*.pm")) {
if ($OSNAME eq 'MSWin32') {
$file =~ s{\\}{/}g;
$sub_modules_path =~ s{\\}{/}g;
}
my ($name) = $file =~ m{$sub_modules_path/(\S+)\.pm$};
next unless $name;
# Don't load Plugin base class
next if $name eq "Plugin";
$self->{logger}->debug($log_prefix . "Trying to load $name Server plugin");
my $module = __PACKAGE__ . "::" . $name;
$module->require();
if ($EVAL_ERROR) {
$self->{logger}->debug($log_prefix . "Failed to load $name Server plugin: $EVAL_ERROR");
next;
}
my $plugin = $module->new(server => $self)
or next;
$plugin->init();
if ($plugin->disabled()) {
$self->{logger}->debug($log_prefix . "HTTPD $name Server plugin loaded but disabled");
} else {
$self->{logger}->info($log_prefix . "HTTPD $name Server plugin loaded");
}
push @plugins, $plugin;
}
# Sort and store loaded plugins
@plugins = sort { $b->priority() <=> $a->priority() } @plugins
if @plugins > 1;
$self->{_plugins} = \@plugins;
return $self;
}
sub setTrustedAddresses {
my ($self, %params) = @_;
# compute addresses allowed for push requests
foreach my $target ($self->{agent}->getTargets()) {
next unless $target->isType('server');
my $url = $target->getUrl();
my $host = URI->new($url)->host();
my @addresses = compile($host, $self->{logger});
$self->{trust}->{$url} = \@addresses;
$self->{logger}->debug("Trusted target ip: ".join(", ",map { $_->print() } @addresses));
}
if ($params{trust}) {
foreach my $string (@{$params{trust}}) {
my @addresses = compile($string, $self->{logger})
or next;
$self->{trust}->{$string} = \@addresses;
$self->{logger}->debug("Trusted client ip: ".join(", ",map { $_->print() } @addresses));
}
}
}
sub _handle {
my ($self, $client, $request, $clientIp, $maxKeepAlive) = @_;
my $logger = $self->{logger};
if (!$request) {
$client->close();
return;
}
my $path = $request->uri()->path();
my $method = $request->method();
$logger->debug($log_prefix . "$method request $path from client $clientIp");
my $keepalive = $request->header('connection') =~ /keep-alive/i;
my $status = 400;
my $error_400 = $log_prefix . "invalid request type: $method";
SWITCH: {
# root request
if ($path eq '/') {
last SWITCH if $method ne 'GET';
$status = $self->_handle_root($client, $request, $clientIp);
last SWITCH;
}
# deploy request
if ($path =~ m{^/deploy/getFile/./../([\w\d/-]+)$}) {
last SWITCH if $method ne 'GET';
$status = $self->_handle_deploy($client, $request, $clientIp, $1);
last SWITCH;
}
# plugins request
foreach my $plugin (@{$self->{_plugins}}) {
next if $plugin->disabled();
if ($plugin->urlMatch($path)) {
undef $error_400;
last SWITCH unless $plugin->supported_method($method);
$status = $plugin->handle($client, $request, $clientIp);
last SWITCH if $status;
}
}
# now request
if ($path =~ m{^/now(?:/(\S*))?$}) {
last SWITCH if $method ne 'GET';
$status = $self->_handle_now($client, $request, $clientIp, $1);
last SWITCH;
}
# status request
if ($path eq '/status') {
last SWITCH if $method ne 'GET';
$status = $self->_handle_status($client, $request, $clientIp);
last SWITCH;
}
# static content request
if ($path =~ m{^/(logo.png|site.css|favicon.ico)$}) {
my $file = $1;
last SWITCH if $method ne 'GET';
$client->send_file_response("$self->{htmldir}/$file");
$status = 200;
last SWITCH;
}
$error_400 = $log_prefix . "unknown path: $path";
}
if ($status == 400) {
$logger->error($error_400) if $error_400;
$client->send_error(400)
}
$logger->debug($log_prefix . "response status $status") unless $status == 1;
if ($status == 200 && $keepalive && --$maxKeepAlive) {
# Looking for another request
$request = $client->get_request();
$self->_handle($client, $request, $clientIp, $maxKeepAlive) if $request;
}
$client->close();
}
sub _handle_plugins {
my ($self, $client, $request, $clientIp, $plugins, $maxKeepAlive) = @_;
my $logger = $self->{logger};
if (!$request) {
$client->close();
return;
}
my $path = $request->uri()->path();
my $method = $request->method();
my $keepalive = $request->header('connection') =~ /keep-alive/i;
$logger->debug($log_prefix . "$method request $path from client $clientIp via plugin");
my $status = 400;
my $match = 0;
foreach my $plugin (@{$plugins}) {
next if $plugin->disabled();
if ($plugin->urlMatch($path)) {
$match = 1;
last unless ($plugin->supported_method($method));
$status = $plugin->handle($client, $request, $clientIp);
last if $status;
}
}
if ($status == 400) {
$logger->error($log_prefix . "unknown path: $path") unless $match;
$client->send_error(400);
$status = 400;
}
# Don't log status if we forked
$logger->debug($log_prefix . "response status $status") unless $status == 1;
if ($status == 200 && $keepalive && --$maxKeepAlive) {
# Looking for another request
$request = $client->get_request();
$self->_handle_plugins($client, $request, $clientIp, $plugins, $maxKeepAlive) if $request;
}
$client->close();
}
sub _handle_root {
my ($self, $client, $request, $clientIp) = @_;
my $logger = $self->{logger};
my $template = Text::Template->new(
TYPE => 'FILE', SOURCE => "$self->{htmldir}/index.tpl"
);
if (!$template) {
$logger->error(
$log_prefix . "Template access failed: $Text::Template::ERROR"
);
my $response = HTTP::Response->new(
500,
'KO',
HTTP::Headers->new('Content-Type' => 'text/html'),
"No template"
);
$client->send_response($response);
return 500;
}
my @server_targets =
map { { name => $_->getUrl(), date => $_->getFormatedNextRunDate() } }
grep { $_->isType('server') }
$self->{agent}->getTargets();
my @local_targets =
map { { name => $_->getPath(), date => $_->getFormatedNextRunDate() } }
grep { $_->isType('local') }
$self->{agent}->getTargets();
my @httpd_plugins = map { @{$_->{plugins}} } values(%{$self->{listeners}});
push @httpd_plugins, @{$self->{_plugins}};
my @listening_plugins =
map { { port => $_->config('port') || $self->{port}, name => $_->name() } }
grep { ! $_->disabled() }
@httpd_plugins;
my $hash = {
version => $FusionInventory::Agent::Version::VERSION,
trust => $self->_isTrusted($clientIp),
status => $self->{agent}->getStatus(),
httpd_plugins => \@listening_plugins,
server_targets => \@server_targets,
local_targets => \@local_targets
};
my $response = HTTP::Response->new(
200,
( run in 0.712 second using v1.01-cache-2.11-cpan-39bf76dae61 )