Цель данной лабораторной работы - показать, как обновлять информацию на веб странице без перезагрузки самой страницы. Для этого нам потребуется веб-браузер и веб-сервер с поддержкой технологии CGI. Я буду использовать веб-сервер lighttpd, который будет установлен на одноплатный компьютер raspberry PI 2. Впрочем, можно использовать абсолютно любой другой. Про CGI можно почитать в интернетах, но вкратце - это технология межпроцессного обмена по каналам stdin и stdout. Применительно к нашему случаю, это означает, что по нашей просьбе веб-сервер запускает CGI программу, передаёт ей какие-то параметры и после выполнения этой программы получает готовые данные. Программой может быть любой исполняемый файл, способный выводить информацию в stdout и получать её из stdin. Такой программой может быть и скрипт при условии, что на компьютере установлен интерпретатор того языка, на котором написан этот скрипт. Это самый простой путь, т.к. не требует предварительной компиляции. Давайте с него и начнем. Будем писать в среде bash. Создадим в домашней директории новый файл. Я пользуюсь текстовым редактором nano. Вы можете использовать любой другой. Код (Bash): nano newfile При этом будет создан файл newfile в домашней папке пользователя и сразу же открыт в редакторе. Например, мы хотим узнать дату и время на нашей малинке. Так и запишем в наш файл: Код (Bash): date Сохраняем файл путем нажатия клавиш ctrl и o. Чтобы выйти из редактора, нажимаем ctrl и х. Теперь, если мы откроем этот файл в интерпретаторе Код (Bash): bash newfile нам будет выведена дата и время на малинке. Теперь запустим наш скрипт на исполнение: Код (Bash): ./newfile но вместо даты и времени мы получим: Код (Bash): -bash: ./newfile: Permission denied Почему? Да потому, что нет прав у нашего скрипта на исполнение. Код (Bash): ls -l newfile -rw-r--r-- 1 pi pi 5 Nov 17 17:34 newfile Давайте их добавим Код (Bash): chmod +x newfile И теперь Код (Bash): ./newfile Sun 17 Nov 18:08:28 MSK 2019 Но на дату смотреть скучно, давайте посмотрим на температуру. Для этого запишем в наш файл: Код (Bash): cat /sys/class/thermal/thermal_zone0/temp Если запустим наш скрипт на исполнение, то увидим примерно такую цифру: Код (Bash): 38470 Чтобы получить градусы, эту цифру необходимо разделить на тысячу. Для этого воспользуемся командой let: Код (Bash): temp=`cat /sys/class/thermal/thermal_zone0/temp` let "temp = temp / 1000" echo "Температура процессора $temp градусов" После запуска скрипт покажет следующее: Код (Bash): Температура процессора 37 градусов Ставим веб-сервер. Я буду использовать lighttpd: Код (Bash): sudo apt install lighttpd Также необходимо установить модуль CGI: Код (Bash): sudo lighty-enable-mod cgi Уже сейчас, если в веб-браузере набрать IP адрес малинки, увидим приветственную страницу веб-сервера. Если порт 80 занят или есть желание его сменить, то делается это в файле lighttpd.conf, который лежит в папке /etc/lighttpd Для поддержки веб-сервером CGI программ, необходимо подкорректировать файл 10-cgi.conf: Код (Bash): nano /etc/lighttpd/conf-enabled/10-cgi.conf У меня содержимое этого файла выглядит так: Код (Bash): server.modules += ( "mod_cgi" ) alias.url += ( "/cgi-bin/" => "/usr/lib/cgi-bin/" ) $HTTP["url"] =~ "^/cgi-bin/" { cgi.assign = ( "" => "/bin/bash" ) } По умолчанию для raspbian (которая на базе debian), местом хранения CGI программ является путь /usr/lib/cgi-bin/. Менять его не будем. После изменения конфигурационного файла, необходимо перезапустить веб-сервер: Код (Bash): sudo /etc/init.d/lighttpd force-reload Давайте переместим наш скрипт в директорию cgi-bin (я использую файловый менеджер mc) и попробуем запустить наш скрипт в веб браузере по следующему адресу: . Если всё сделали правильно, то мы увидим следующее: Всё из-за того, что в нашем случае веб-сервер выступает в качестве шлюза между веб-браузером и CGI программой. Поэтому мы в нашем скрипте допишем необходимую информацию в соответствии с требованиями стандарта HTML5.
Первое, что необходимо указать в скрипте, это с каким интерпретатором его связать: Код (Bash): #!/bin/bash/ На самом деле, мы уже указали путь к интерпретатору, когда составляли конфигурацию в файле 10-cgi.conf. Но по правилам хорошего тона лучше это указывать в файле скрипта, т.к. скрипт может и не быть CGI программой и окружение будет другое. Далее нам необходимо указать веб-серверу, какие данные мы будем отправлять: Код (Bash): echo "Content-type: text/html" echo "" Указатель типа контента отделяется от собственно контента пустой строкой. Формируем стандартный html заголовок: Код (Bash): echo "<!doctype html>" echo "<html lang='en-EN'>" echo "<head>" echo "<title>" echo "raspberry" echo "</title>" echo "<meta charset='utf-8'>" echo "</head>" Далее формируем тело страницы: Код (Bash): echo "<body>" echo "<pre>" date cat /sys/class/thermal/thermal_zone0/temp temp=`cat /sys/class/thermal/thermal_zone0/temp` let "temp = temp / 1000" echo "Температура процессора $temp градусов" echo "</pre>" echo "</body>" echo "</html>" Сохраняем файл, обновляем страницу в браузере: Таким образом, мы научились создавать динамические страницы. Такие страницы нигде не хранятся - они формируются динамически, путем запроса через веб-сервер.
Sun Nov 17 20:19:53 MSK 2019 и Температура процессора 37 градусов Как-то стабильности нет, две локали, и английская и русская. В одной понятнее было-бы, а тут не знаешь во что верить. Толи тебе по русски ответили, толи перед тобой враг А где страница то? Один единственный запрос на /cgi-bin/newfile который получает html контент, не данные о дате и температуре а полный контент для отображения. Если бы загружался контент (который может быть довольно тяжелым, стили скрипты изображения и т.п.) и отдельно подгружались данные о дате и температуре - было-бы очевидно, вот тут статика, которая может быть кэширована браузером, а вот тут данные. Тема "как обновлять информацию на веб странице без перезагрузки самой страницы" по моему не раскрыта. П.С. А ajax то где? Может опечатка и его там нет?
lighttpd при выполнении cgi не контролирует соединение с клиентом. Скрипт может выполняться бесконечно долго, а клиент в это время может разорвать соединение. В этом случае lighttpd почему-то продолжает выполнять скрипт, несмотря на то что клиент уже сдулся. В пример добавил строчку cat /dev/zero, обратился по прежней ссылке, разорвал соединение и уже минут 30 для одного запроса lighttpd продолжает выполнять cgi, при этом употребляет много процессорного времени и памяти. Определяю Main PID сервиса Код (Text): pi@raspberrypi:~ $ systemctl status lighttpd ● lighttpd.service - Lighttpd Daemon Loaded: loaded (/lib/systemd/system/lighttpd.service; enabled; vendor preset: enabled) Active: active (running) since Mon 2019-11-18 06:55:05 GMT; 33min ago Process: 7333 ExecStartPre=/usr/sbin/lighttpd -tt -f /etc/lighttpd/lighttpd.conf (code=exited, status=0/SUCCESS) Main PID: 7339 (lighttpd) Tasks: 3 (limit: 2200) Memory: 788.9M CGroup: /system.slice/lighttpd.service ├─7339 /usr/sbin/lighttpd -D -f /etc/lighttpd/lighttpd.conf ├─7469 /bin/bash /usr/lib/cgi-bin/newfile └─7473 cat /dev/zero Смотрю на top для этого процесса Код (Text): pi@raspberrypi:~ $ top -p 7339 top - 07:34:22 up 19:31, 2 users, load average: 4.61, 4.75, 4.44 Tasks: 1 total, 1 running, 0 sleeping, 0 stopped, 0 zombie %Cpu(s): 0.0 us, 13.0 sy, 0.0 ni, 70.5 id, 16.5 wa, 0.0 hi, 0.0 si, 0.0 st MiB Mem : 926.1 total, 34.4 free, 122.4 used, 769.2 buff/cache MiB Swap: 100.0 total, 83.2 free, 16.8 used. 738.9 avail Mem PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 7339 www-data 20 0 71712 65760 2052 R 29.9 6.9 3:08.83 lighttpd
Теперь у нас возникает закономерный вопрос - а если в верстке сайта появилось желание что-то изменить, добавить страничку или верстальщик, дизайнер - это совсем другие люди? И вот здесь мы подходим к технологиям, которые позволяют подгружать контент в созданный заранее шаблон. Одна из них - AJAX. AJAX - это Asynchronous Javascript and XML. Другими словами, происходит асинхронный запрос (синхронный не используется практически - он блокирует страницу полностью до получения ответа от сервера) с помощью объекта XMLHttpRequest, который поддерживается всеми современными браузерами. Экземпляр этого объекта и вызов его методов осуществляется с помощью JavaScript скриптов. Какую информацию мы можем запросить у сервера? Как следует из расшифровки аббревиатуры - XML. Но это не так. Информация может быть любой: Извлечь информацию из ответа от сервера мы можем четырьмя методами: responseText responseType responseURL responseXML Как мы помним, наш скрипт newfile возвращает простой текст, поэтому мы будем использовать свойство responseText. Но прежде нам необходимо объявить экземпляр объекта XMLHttpRequest: Код (Javascript): var xhttp = new XMLHttpRequest(); Далее, нам необходимо отследить получение браузером ответа от сервера. Для этого воспользуемся свойством onreadystatechange. Это свойство меняется каждый раз, когда меняется статус объекта XMLHttpRequest. Всего статусов может быть пять: 0 — Объект не инициализирован. 1 — Объект загружает данные. 2 — Объект загрузил свои данные. 3 — Объект не полностью загружен, но может взаимодействовать с пользователем. 4 — Объект полностью инициализирован; получен ответ от сервера. Нам нужен последний статус под номером 4. Также нам нужен успех выполнения запроса сервером, т.е. ответ 200. В результате у нас получается такая конструкция: Код (Javascript): xhttp.onreadystatechange = function() { if (this.readyState == 4 && this.status == 200) { document.getElementById("main-text").innerHTML = this.responseText; } }; Для того, чтобы вставить полученную информацию в веб страницу (в загруженный шаблон), мы попросим браузер отыскать для нас элемент на этой странице по его ID. У меня это "main-text". Чтобы вставить (с полной заменой) текст, воспользуемся методом innerHTML. Всё, что написано выше, произойдет лишь только в том случае, если мы сделаем запрос веб-серверу. Как правило, используют два типа запроса: GET и POST. POST используется в том случае, если нам надо передать на веб-сервер много данных. В нашем случае мы передаем строку URI, поэтому воспользуемся методом GET. Инициализируем соединение с веб-сервером: Код (Javascript): xhttp.open("GET", "cgi-bin/newfile", true); и отправляем запрос: Код (Javascript): xhttp.send(); Вот, собственно, и всё. Далее нам осталось сверстать простую HTML страницу и научить браузер обновлять на ней информацию без её перезагрузки.
Пытался понять можно ли использовать кроме метода get другой метод - post (put delete не использовал, не актуально) так и не понял. Где (точнее как) получить в скрипте заголовки запроса? Если post запрос выполнять можно, то как правильно направить поток из запроса в скрипт (в его stdin)?
Давайте создадим простенькую HTML форму? которая будет принимать данные от нашей CGI программы. Говорю сразу: я ни разу не дизайнер, не верстальщик, но сделал как сделал. Код лежит здесь (только body (без тегов <body> !!!): https://codepen.io/dAshkova/pen/BaavLmz Вначале HTML файла необходимо вставить заголовок с ссылками на css файл и на файл скрипта (у меня он называется GetCGI.js). В результате получится следующее : HTML: <!doctype html> <html lang='en-EN'> <head> <title> GPIO </title> <meta charset='utf-8'> <script src="scripts/GetCGI.js"></script> <link rel="stylesheet" href="css/style.css"> </head> <body> <header> <h2> CGI, AJAX и RASPI2 </h2></header> <div class="grid"> <div class="menu"> Здесь будет меню </div> <div class="main"> <h2>RASPBERRY PI</h2> <div class="image"> <img src="https://www.raspberrypi.org/homepage-9df4b/static/fb5ac6aeb0846cf50b466739c8e68327/bc3a8/162f80fba36c0749bc2d8e04f40e8b12c030c50d_raspberry-pi-2-1-1621x1080.jpg"> <div class="caption"> <span> <div id="main-text" class="temp-text"> Подождите... </div> </span> </div> </div> </div> <div> Здесь тоже что-то будет </div> </div> </body> </html> Менять содержимое мы будем в этом слое: HTML: <div id="main-text" class="temp-text"> Подождите... </div> Т.е. JS скрипт будет искать элемент с id="main-text" и внедрять туда новое содержимое. JS скрипт у нас такой: Код (Javascript): function loadCGI() { var xhttp = new XMLHttpRequest(); xhttp.onreadystatechange = function() { if (this.readyState == 4 && this.status == 200) { document.getElementById("main-text").innerHTML = this.responseText; } }; xhttp.open("GET", "cgi-bin/newfile", true); xhttp.send(); } Файл style.css кладем в папку css, скрипт в папку scripts. Картинку я сдернул прямой ссылкой с сайта распберри (я думаю, они не обидятся)) Обе эти папки и файл index.html кладем сюда: Код (Bash): /var/www/html/ Теперь при попытке открыть домашнюю страницу мы увидим следующее: И... ничего не работает. Почему? Потому что веб-браузер не знает, когда запускать нашу JS функцию. Поэтому ему надо чистаканкоетна указать в какой момент и какую функцию запускать на выполнение. А запускать функцию JS мы будем только после полной загрузки HTML страницы. Для этого необходимо HTML: <body> заменить на HTML: <body onload="loadCGI;"> Теперь мы увидим следующее Но так мы получим данные лишь однажды - после загрузки страницы. А где же обещанная динамическая подгрузка? А для этого браузер надо поставить на счетчик чистаканкретна. Открываем файлик с JS скриптом и допишем ещё одну функцию: Код (Javascript): function callCGI() { setInterval( () => {loadCGI();}, 5000 ); } Здесь мы включили таймер и он каждые 5 секунд будет запускать указанную функцию. А чтобы этот таймер запустить, необходимо всё в том же index.html заменить в body HTML: <body onload="callCGI();"> Ну вот и всё.
Друже, мне все нравится, но что это было? Зачем тренироваться писать веб-страницы? Все, что ли, через эту хрень проходят?
Думаю, топик был создан для того, чтоб показать как использовать cgi работая через lighttpd, если у пользователя не возникает сложностей для сбора данных и вывода их в стандартный поток вывода, то использование cgi скриптов дает быстрый старт для получения собранных данных через веб интерфейс. А страницы как раз и позволяют это показать (если не тренироваться писать пример то что тогда использовать?). И без страниц всё получается, curl-ом выполняю запросы и получаю адекватные данные, но показывать как командной строкой получить данные - не очень красиво. А тут в примере показано и использование cgi скриптов и доступ к ним через ajax. Если это не так, думаю автор меня поправит. Если неожиданно оборудование перестанет отвечать на запросы, его просто выключат например, то очередной запрос от браузера повиснет в ожидании, тем временем каждые 5 секунд браузер будет отправлять новые запросы, которые тоже будут повисать. Если использовать не интервал, а setTimeout и в обработчике результата выполнения запроса снова выполнять setTimeout то множественных подвисших запросов можно избежать.
setTimeout он не блокирующий Этим самым setTimeout говорим браузеру, через 5 секунд выполни ещё раз loadCGI которая тоже не блокирующая, вы явно указываете xhttp.open("GET", "cgi-bin/newfile", true); true - выполнять асинхронно П.С. Даже если запрос будет выполняться синхронно, то во время выполнения запроса браузер "замерзнет", а когда запрос будет выполнен (успешно или нет) дадим браузеру ещё раз задание выполнить тот же код через таймаут.
От задачи зависит. Ибо здесь малина видна, а для нее туча готовых решений. Данные собирать? NodeRed, Mqtt.
можно дальше продолжить ассоциативный ряд: зачем малина и все эти nodered, mqtt, если есть туча готовых решений - KNX, Z-Wave, Jablotron, AJAX, En-ocean...