Просто хочу показать, как с помощью Perl можно сделать простой веб-сервис, на примере мониторинга свободного места на диске и свободной памяти.
Почему именно на Perl - потому что на примере минимального дистрибутива Debian, где Perl уже установлен по умолчанию, и для создания вебсервиса потребуются минимальные усилия.
Ну и заодно - как делаются библиотеки функций и автотесты.
Итак, сам Perl установлен в дистрибутиве.
Устанавливаем дополнительно фреймворк Mojolicious:
apt install libmojolicious-perl
Он займет дополнительно около 70 мегабайт места, но значительно сэкономит время "разработки" данного вебсервиса.
Как уже говорил, в данном примере просто контролируем свободное место на диске и доступную память. Разумеется, задача может быть любой другой, это просто пример.
Чтобы не изобретать велосипед - данные будем получать через системные утилиты, просто добавим обертку над ними, в виде двух функций.
Но это пока еще не веб-приложение, это просто пара функций. Чтобы не забивать себе голову, куда и как их добавить - вынесем их в отдельный модуль, который назовем MyStat.pm
#!/usr/bin/perl
# название модуля должно соответствовать имени файла MyStat.pm
package MyStat;
# Получение информации о свободном месте на диске
sub get_disk_space {
my $df_output = `df -h /`; # Получаем информацию о корневом разделе
my @lines = split("\n", $df_output);
my @fields = split(/\s+/, $lines[1]);
return $fields[3]; # Возвращаем доступное пространство
}
# Получение информации о доступной памяти
sub get_memory {
my $free_output = `free -h`; # Получаем информацию о памяти
my @lines = split("\n", $free_output);
my @fields = split(/\s+/, $lines[1]);
return $fields[6]; # Возвращаем доступную память
}
# модуль всегда завершается возвратом 1
1;
Теперь в любой программе достаточно будет подключить этот модуль и вызвать функции, примерно так:
#!/usr/bin/perl
# эта строчка нужна чтобы указать, где лежит модуль,
# если он не находится в системных каталогах типа /usr/share/perl...
# в данном случае он лежит в каталоге lib в домашнем каталоге пользователя
use lib $ENV{HOME}.'/lib';
# подключаем его
use MyStat;
# получаем данные
my $df = MyStat::get_disk_space;
my $mem = MyStat::get_memory;
....
Теперь напишем для модуля автотест. Это необязательно, но когда со временем модули разрастаются, усложняются и изменяются - успешное прохождение автотеста поможет выявить баг до того, как обновление поломает работующую систему.
И лучше это делать сразу - потом будет просто некогда.
Традиционно, его можно назвать MyStat.t, но вообще-то это непринципиально, просто так удобнее понять потом что это было.
#!/usr/bin/perl
use lib $ENV{HOME}.'/lib';
use MyStat;
use Test::More;
my $df = MyStat::get_disk_space;
ok(defined $df, "disk space is $df");
my $mem = MyStat::get_memory;
ok(defined $mem, "free memory is $mem");
done_testing();
По сути - это обычный скрипт, который просто вызывает функции, а команда ok проверяет, то ли мы получили что хотели или нет.
Если выполнить команду perl MyStat.t - получим примерно следующее:
ok 1 - disk space is 70G
ok 2 - free memory is 3.3Gi
1..2
Первый тест ОК, результат "70G", второй тест ОК, результат 3.3Gi.
Оба теста успешно отработали, вернули значения.
Модуль исправен.
Теперь делаем собственно вебсервис: он будет по запросу выдавать JSON с данными о наличии свободного места.
Для этого выполняем команду:
mojo generate lite-app MyApp
У нас получилась заготовка мини-приложения MyApp.pl
#!/usr/bin/env perl
use Mojolicious::Lite -signatures;
get '/' => sub ($c) {
$c->render(template => 'index');
};
app->start;
__DATA__
@@ index.html.ep
% layout 'default';
% title 'Welcome';
<h1>Welcome to the Mojolicious real-time web framework!</h1>
@@ layouts/default.html.ep
<!DOCTYPE html>
<html>
<head><title><%= title %></title></head>
<body><%= content %></body>
</html>
В данный момент приложение содержит один единственный метод GET "/", который отрисовывает шаблон index, причем сам шаблон определен в этом же файле, в секции __DATA__ , и состоит из двух частей: шаблона самой страницы, @@ index.html.ep, и шаблона макета default.html.ep.
Такая двойная конструкция позволяет засунуть в макет общие для всего приложения стили, скрипты и прочее, а рендеринг отдельных страниц делать отдельными шаблонами, используя этот макет, или другой, или без макета выводить просто кусок текста, или вообще обходиться без шаблонов.
Но сейчас и всего этого не нужно, рисовать отдельные страницы мы не будем, просто выведем JSON.
#!/usr/bin/env perl
use Mojolicious::Lite -signatures;
use Mojolicious::Static;
# ==============================================
use lib $ENV{HOME}.'/lib';
use MyStat;
get '/getstat' => sub ($c) {
my $mem = MyStat::get_memory;
$mem =~ s/([\d\.]+).*/$1/;
my $drive = MyStat::get_disk_space;
$drive =~ s/([\d\.]+).*/$1/;
my $t = {
mem => $mem,
drive => $drive,
};
$c->res->headers->header('Access-Control-Allow-Origin' => '*');
$c->render(json => $t);
};
# ==============================================
get '/' => sub ($c) {
$c->render(template => 'index');
};
app->start;
__DATA__
........
Добавлен метод GET "/getstat" который выводит строку наподобие {"mem":2.6,"drive":102.4}
Для этого он вызывает ранее созданные функции из модуля, получает данные, отрезает от них буквы и формирует JSON.
Его можно вызвать из внешней системы, чтобы получить данные о ресурсах на этом сервере.
Примерно по такой же схеме можно добавить самые разные вебсервисы, включая запросы к какой-нибудь базе данных или что-то еще подобное. Вопрос только в добавлении методов и функций.
А теперь добавим на "главную страницу" простейшие графики, чтобы можно было сразу в графическом виде смотреть, что оно там нам выдает.
Для этого создаем обыкновенную html-страницу с javascript, вызывающим созданный ранее метод и подставляющим данные в график. А потом внедряем ее в скрипт:
__DATA__
@@ layouts/default.html.ep
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Memory and Disk Usage</title>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<style>
body {
font-family: Arial, sans-serif;
margin: 20px;
}
canvas {
max-width: 600px;
margin: 20px auto;
display: block;
}
</style>
</head>
<body><%= content %></body>
</html>
@@ index.html.ep
% layout 'default';
% title 'Welcome';
<h1>System Monitoring</h1>
<p>Графики загруженности памяти и занятости диска</p>
<canvas id="memChart"></canvas>
<canvas id="diskChart"></canvas>
<script>
// Конфигурация графиков
const maxDataPoints = 1000; // Максимальная ширина графика (600 измерений)
// Инициализация графика
const memChartCtx = document.getElementById('memChart').getContext('2d');
const memChart = new Chart(memChartCtx, {
type: 'line',
data: {
labels: Array(maxDataPoints).fill(''), // Метки пустые, обновляем по мере необходимости
datasets: [{
label: 'Mem free (Gb)',
borderColor: 'rgb(75, 192, 192)',
tension: 0.1,
data: [] // Данные свободной памяти
}]
},
options: {
responsive: true,
scales: {
x: { display: false },
y: { min: 0 }
}
}
});
// Инициализация графика использования диска
const diskChartCtx = document.getElementById('diskChart').getContext('2d');
const diskChart = new Chart(diskChartCtx, {
type: 'line',
data: {
labels: Array(maxDataPoints).fill(''), // Метки пустые, обновляем по мере необходимости
datasets: [{
label: 'Disk Usage (Gb)',
borderColor: 'rgb(255, 99, 132)',
tension: 0.1,
data: [] // Данные использования диска
}]
},
options: {
responsive: true,
scales: {
x: { display: false },
y: { min: 0 }
}
}
});
// Функция для добавления новых данных на график
function updateChart(chart, newData) {
if (chart.data.datasets[0].data.length >= maxDataPoints) {
chart.data.datasets[0].data.shift(); // Удаляем старые данные
chart.data.labels.shift();
}
chart.data.datasets[0].data.push(newData); // Добавляем новые данные
chart.data.labels.push(''); // Пустая метка
chart.update();
}
// Функция для запроса данных с сервера
async function fetchData() {
try {
const response = await fetch('/getstat');
if (!response.ok) throw new Error('Network response was not ok');
const { mem, drive } = await response.json();
updateChart(memChart, mem);
updateChart(diskChart, drive);
} catch (error) {
console.error('Ошибка при запросе данных:', error);
}
}
// Запрашиваем данные раз в 30 секунд
setInterval(fetchData, 1000);
// Первоначальный запуск
fetchData();
</script>
По сути, в данном случае при запросе к "/" сервер просто выдаст страничку, которая начнет запрашивать "/getstat" и заполнять графики.
А теперь всё это запустим, например так:
morbo -l http://*:8080 MyApp.pl
При подключении браузером к порту 8080 получим примерно такую страницу:
В принципе ничего не мешает сделать страницу красивой, настроить Nginx frontend - и получится небольшое веб-приложение, возможно с десятком страниц и методов. Для большего лучше создавать приложение чуть по другому, но это другая история.
Ну а для нужд внутреннего мониторинга можно оставить и так.
Писать этот текст было дольше...
Di-Ger
Для "минимальной" реализации, а тем более для вашего примера, установка Mojo, на мой взгляд избыточна. Для поднятия веб-сервиса достаточно воспользоваться IO::Socket::INET
А html шаблон парсить стандартными средствами Perl.