Просто хочу показать, как с помощью 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 — и получится небольшое веб-приложение, возможно с десятком страниц и методов. Для большего лучше создавать приложение чуть по другому, но это другая история.
Ну а для нужд внутреннего мониторинга можно оставить и так.
Писать этот текст было дольше…
ссылка на оригинал статьи https://habr.com/ru/articles/873688/
Добавить комментарий