Забытые технологии: CGI
Навеяло комментариями: у людей стойкая ассоциация между Perl как языком и CGI как технологией, использовавшейся в начале веб-времен.
Всё логично: на тот момент Perl был одним из немногих распространенных скриптовых языков, и естественно что на нем делать CGI-скрипты было удобнее, чем на shell, например.
Но это не означает что одно к другому было гвоздями прибито.
А вообще технология была по-своему хорошая: установили вебсервер (как правило — Apache), настроили каталог, из которого запускаются скрипты — и запускай что хочешь.
Причем в отличии от более поздних сложных вебсистем — это был буквально запуск программы на сервере, точно так же, как вы бы запустили ее через терминал или из консоли. Все, что передавалось в программу через HTTP — она получала через STDIN, всё что она выводила в STDOUT — прилетало клиенту в браузер.
По сути обычная консольная неинтерактивная программа, чистый REST API — сервер всегда начинает выполнение программы с чистого листа, и программа работает с тем что ей передали.
Минус был в том, что на запуск программы всегда нужно какое-то время, особенно если она написана на скриптовом языке, требующем обработки интерпретатором.
Для разовых запросов это не критично, но когда программа стартует хотя бы 0.5 секунды, а у вас идет 100 запросов в секунду — всё начинает немного тормозить.
Поэтому позже появилась технология FastCGI, когда программа «подвешивалась» в пред-запущенном состоянии и ждала данных, а потом и вовсе перешли на встроенные сервера с многопоточностью.
А сейчас покажу, как можно использовать это в наши дни:
Как уже писал в предыдущей статье — настроил себе «универсальный блок доступа»: компьютер-одноплатник с маршрутизирующим прокси-сервером, который отделяет агнцев от козлищ, мух от котлет, а больных от здоровых.
Но, как и любая программа — все эти навороты могут потребовать перезапуска. К тому же домашний интернет сам по себе штука нестабильная, то дерево на провода упало, то экскаватор что-то не то выкопал, пойди пойми почему вдруг перестали грузиться котики.
Надо понимать что произошло.
Конечно, всегда можно зайти по ssh, набрать в консоли несколько заклинаний и всё узнать, но можно же это облегчить?
При этом писать отдельную вебсистему, которая будет всё проверять — как-то черезчур.
Итак, ТЗ:
— имеются несколько программ, запускаемых в фоне
— имеются методы проверки, работает ли программа или «зависла»
— требуется сделать простенькую вебстраницу с текущим статусом и с возможностью перезапуска программ.
— поскольку все это глубоко внутри локальной сети — авторизация не требуется, но и поломать кривыми ручонками никто ничего не должен.
Устанавливаем на этом сервере Apache (Nginx без плясок с бубном простой CGI не умеет):
apt install apache2
После установки там всё работает «из коробки», нужно только сделать симлинк на cgi-модуль, по умолчанию он отключен:
cd /etc/apache2/mods-enabled
ln -s …/mods-available/cgi.load .
/etc/init.d/apache2 restart
Готово. По умолчанию скрипты CGI должны лежать в /usr/lib/cgi-bin/, а через веб доступны как /cgi-bin/*.cgi
Разумеется можно поменять, особенно если устаревшее «cgi-bin» глаз режет -, но сейчас мы его и не увидим. И разумеется, сразу в этом каталоге пусто.
Создаем первый файл, env.cgi:
#!/bin/sh
echo "Content-Type: text/html";
echo ""
echo ""
env
echo "
»
id
chmod 755 env.cgi
Заходим браузером на сервер:
http://xx.xx.xx.xx/cgi-bin/env.cgi
GATEWAY_INTERFACE=CGI/1.1
REMOTE_ADDR=XX.XX.XX.XX
QUERY_STRING=
HTTP_USER_AGENT=Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36
DOCUMENT_ROOT=/var/www/html
REMOTE_PORT=40472
HTTP_UPGRADE_INSECURE_REQUESTS=1
HTTP_ACCEPT=text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
SERVER_SIGNATURE=
Apache/2.4.62 (Debian) Server at 10.1.0.4 Port 80
....
uid=33(www-data) gid=33(www-data) groups=33(www-data)
Никаких сложных программ, новых или старых языков, фреймворков, ничего, простой shell-скрипт.
Тут интересна строка QUERY_STRING= и нижняя uid=33(www-data) gid=33(www-data) groups=33(www-data)
В QUERY_STRING= помещается строка, которая будет введена в адресе после имени скрипта:
http://XX.XX.XX.XX/cgi-bin/env.cgi? blablabla → QUERY_STRING=blablabla
По-хорошему это должны быть urlencoded-параметры GET запроса, но по факту вообще не имеет значения что за строка там будет, лишь бы не нарушать соглашения по символам.
В данном случае туда можно отправлять единственную строку, указывающую на то, с какой именно программой должен работать наш скрипт.
А нижняя строка, результат команды id — показывает, что вебсервер работает под пользователем www-data.
Для проверки работоспособности интернета и различных прокси-вариантом можно использовать стандартный curl:
Запрос «напрямую» даст нам внешний IP, под которым нас видно в интернете:
curl http://v4v6.ipv6-test.com/api/myip.php --silent
Запрос через sock5-прокси даст нам внешний IP, под которым нас видно через прокси, а также проверит работоспособность самого прокси:
curl -x socks5h://127.0.0.1:1080 http://v4v6.ipv6-test.com/api/myip.php --silent
Остальные — по аналогии.
Можно сделать все проверки в одном скрипте и вернуть в JSON-формате, но некоторые запросы могут отрабатывать долго, и скрипт будет ждать их все. Это не очень удобно, поэтому скрипт будет всё-таки один, но что именно он проверяет — будет зависить от параметра запроса.
Таким образом, получаем скрипт check_one.cgi
#!/bin/sh
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin; export PATH
# обязательный заголовок
echo "Content-Type: application/json"
echo ""
# к параметру добавляем "x" чтобы избежать пустых строк
if [ "x$QUERY_STRING" = "xxray" ] ; then
# тут запрашиваем ответ сервера, нам дадут IP адрес
INETIP=$( curl http://v4v6.ipv6-test.com/api/myip.php --silent )
PROXYIP=$( curl -x socks5h://127.0.0.1:1080 http://v4v6.ipv6-test.com/api/myip.php --silent )
echo "{\"extip\":\"${INETIP}\",\"proxyip\":\"${PROXYIP}\"}"
elif [ "x$QUERY_STRING" = "xi2p" ] ; then
X=1
# тут и далее - важен сам факт ответа, поэтому только заголовки и код ошибки
curl -x socks5h://127.0.0.1:4447 http://flibusta.i2p -I --silent -o /dev/null
if [ $? -ne 0 ] ; then
X=0
fi
echo "{\"i2p\":${X}}";
elif [ "x$QUERY_STRING" = "xnodpi" ] ; then
X=1
curl -x socks5h://127.0.0.1:1081 https://jnn-pa.googleapis.com -I --silent -o /dev/null
if [ $? -ne 0 ] ; then
X=0
fi
echo "{\"nodpi\":${X}}";
elif [ "x$QUERY_STRING" = "xproxy" ] ; then
X=1
curl -x socks5h://127.0.0.1:6007 http://v4v6.ipv6-test.com/api/myip.php -I --silent -o /dev/null
if [ $? -ne 0 ] ; then
X=0
fi
echo "{\"proxy\":${X}}";
else
# чтобы не забыть что можно указывать
echo "{\"options\":[\"proxy\",\"xray\",\"i2p\",\"nodpi\"]}"
fi
Теперь, указывая те или иные опции, получим ответ, работает тот или иной маршрут или нет.
Перезапуск сервисов можно сделать так:
Поскольку вебсервер работает под юзером www-data — прав перезапускать всё что хочется у него нет, и давать их ему мы не будем.
Вместо этого запустим сервисы конкретно под ним, и так, чтобы они перестартовывали сами.
Делаем типовые скрипты наподобие:
#!/bin/sh
#
exec > /dev/null
exec 2>&1
cd /tmp
while [ 1 ] ; do
/usr/local/etc/xray/xray -c /usr/local/etc/xray/config.json
done
Программа xray запускается не в фоне, поэтому если убить процесс, или он сам умрет — бесконечный цикл в скрипте запустит его снова. Таким образом страхуемся от нештатных падений, а в случае зависания достаточно просто убить зависший процесс, прав на это хватит.
Полностью аналогично — для других процессов.
Остается запустить процессы. Для этого удобно использовать скрипт /etc/rc.local (в данном случае он работает, если нет — ну в другое место добавить, в /etc/init.d например)
su www-data -s /bin/sh -c 'setsid /usr/local/bin/start_xray &'
Параметр -s /bin/sh нужен потому, что у пользователя www-data своего шелла нет (см. vipw)
Параметр -с запускает команду, в данном случае setsid, который запускает скрипт запуска (масло масляное на масле) демоном.
Остальные — по аналогии. После рестарта компьютера запускаются стартовые скрипты, которые запускают программы, которые теперь работают под юзером, и в случае их падения или убийства автоматически рестартуют.
Единственный нюанс с i2pd — он ищет конфиги в домашнем каталоге, в ~/.i2pd, а для данного юзера это каталог /var/www (снова см. vipw), именно там должны лежать настройки и там должны быть права доступа на запись в каталог настроек.
Остается написать скрипт-киллер:
#!/bin/sh
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin; export PATH
echo "Content-Type: text/html"
echo ""
pid=
if [ "x$QUERY_STRING" = "xxray" ] ; then
pid=$(ps ax| grep -v grep | grep 'xray/xray' | awk '{ print $1 }')
if [ -n "$pid" ] ; then
echo $pid
kill $pid
sleep 3
fi
elif [ "x$QUERY_STRING" = "xi2p" ] ; then
pid=$(ps ax| grep -v grep | grep '/i2pd' | awk '{ print $1 }')
if [ -n "$pid" ] ; then
echo $pid
kill $pid
sleep 3
fi
elif [ "x$QUERY_STRING" = "xnodpi" ] ; then
pid=$(ps ax| grep -v grep | grep '/ciadpi' | awk '{ print $1 }')
if [ -n "$pid" ] ; then
echo $pid
kill $pid
sleep 3
fi
else
echo "{\"options\":[\"xray\",\"i2p\",\"nodpi\"]}"
fi
ps ax дает список процессов, первый «grep» убирает из выдачи сам grep, второй «grep» ищет искомую программу, awk вытаскивает PID, kill его убивает, sleep для того, чтобы чуть подождать пока процесс умрет.
В общем практически всё, остается изобразить страничку:
HTML gate
External IP
...
Proxy IP
...
I2P network
...
Proxy
...
NoDPI
...
Как-то так
Чистое админство, shell и немного javascript — и получился вполне работающий веб-интерфейс.
А если в начинку не лезть — то и не видно, что это древний допотопный CGI.
Habrahabr.ru прочитано 945 раз