Простой вебсервис, на Perl, но не CGI

от автора

Просто хочу показать, как с помощью 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/