Развитие веба начиналось с CGI‑скриптов. Они были основой веб‑программирования на заре становления веб‑приложений. Сегодня CGI заменён на более производительные и современные методы запуска серверного кода, но старый добрый CGI ещё крепок и может решать некоторые задачи. А вы знали, что в Angie можно запускать CGI‑скрипты без проксирования?

Навигация по циклу

  1. Почему стоит переходить на Angie.

  2. Установка Angie из пакетов и в докере.

  3. Переезд с Nginx на Angie. Пошаговая инструкция.

  4. Настройка location в Angie. Разделение динамических и статических запросов.

  5. Перенаправления в Angie: return, rewrite и примеры их применения.

  6. Сжатие текста в Angie: статика, динамика, производительность.

  7. Серверное кэширование в Angie: тонкости настройки.

  8. Настройка TLS в Angie: безопасность и скорость.

  9. Настройка Angie в роли обратного HTTP-прокси.

  10. Балансировка нагрузки для HTTP(S) в Angie.

  11. Мониторинг Angie с помощью Console Light и API.

  12. Балансировка и проксирование L4-трафика в Angie.

  13. Клиентское кэширование в Angie.

  14. Динамические группы проксируемых серверов в Angie.

  15. Мониторинг Angie с Prometheus и Grafana.

  16. Отказоустойчивый кластер Angie с VRRP и Keepalived.

  17. Контроль доступа в Angie.

  18. Аутентификация клиентов в Angie с помощью TLS-сертификатов.

  19. Кастомизация Angie (njs, Lua, Perl).

  20. Запуск CGI-скриптов в Angie.

Видеоверсия

Для вашего удобства подготовлена видеоверсия этой статьи, доступна на Rutube, VKVideo и YouTube.

Что такое CGI?

Common Gateway Interface (CGI) — это интерфейс, позволяющий запускать программный код на сервере при работе по HTTP(S). Как правило, таким образом запускаются скрипты (программы на интерпретируемых языках), но также может использоваться любая скомпилированная программа, подчиняющаяся соответствующей спецификации.

Логика взаимодействия максимально простая: все данные от пользователя поступают на вход программы в виде переменных окружения и стандартного потока ввода (STDIN), а вывод программы (STDOUT) отправляется как ответ клиенту. Некоторые переменные окружения определяются на основе свойств запроса (REMOTE_ADDR, REQUEST_METHOD, SCRIPT_NAME и так далее; параметры GET‑запросов попадают в переменную QUERY_STRING). 

Для CGI можно использовать практически любой скриптовый язык программирования, исполняемый на сервере: Bash, Perl, PHP, Python и так далее.

Запускаем Bash-скрипт через CGI

Традиционный путь работы с CGI‑скриптами сводится к установке веб‑сервера Apache и его стандартного модуля CGI для работы со скриптами. Далее в Angie можно настроить проксирование запросов со скриптами на Apache. Однако, для простых задач это будет избыточным и громоздким решением.

Запуск CGI‑скриптов в Angie обеспечивает сторонний модуль CGI; подробная документация к модулю находится на странице проекта.

Установим модуль из репозитория Angie:

apt install angie-module-cgi

Подключим его в начале основного конфига angie.conf:

load_module modules/ngx_http_cgi_module.so;

Теперь создаём тестовый скрипт на Bash (например, для вывода переменных окружения) по адресу /var/www/html/cgi/test.sh :

#!/bin/sh
echo "Content-Type: text/plain" # Добавить заголовок в ответе
echo "" # Разделитель заголовков и тела ответа

# Переменные окружения
echo "query string: $QUERY_STRING"
echo "server addr: $SERVER_ADDR"
echo "server port: $SERVER_PORT"

# Заголовки запроса через переменные окружения
echo "http host: $HTTP_HOST"
echo "http accept: $HTTP_ACCEPT"
echo "http Some-Field: $HTTP_SOME_FIELD"

body=$(cat) # Считывает тело запроса в переменную
echo "Request body: $body"

Не забываем назначить права на исполнение для скрипта.

chmod +x /var/www/html/cgi/test.sh

Как видно из примера, скрипт может обращаться к переменным окружения, которые определяются заголовками запроса и другими параметрами HTTP‑запроса. Теперь осталось настроить обработку CGI‑скриптов в директории /var/www/html/cgi. Для этого создаём сервер с локацией /cgi/:

server {
listen 80;
root /var/www/html;
	location /cgi/ {
        	cgi on;
	}
}

Единственная директива из модуля CGI — это cgi on в локации /cgi/. При использовании CGI‑скриптов нужно очень внимательно относиться к безопасности директорий, в которых разрешено исполнять скрипты. Можно выносить такие директории за пределы корня сайтов, чтобы усложнить попадание вредоносного кода. Конечно, данным пользователя в таких скриптах также нельзя доверять, поэтому необходимо валидировать всех входящие параметры.

Проверим работу скрипта через curl. Первый запрос без тела запроса и параметров:

curl http://localhost/cgi/test.sh

query string: 
server addr: 127.0.0.1
server port: 80
http host: localhost
http accept: */*
http Some-Field: 
Request body: 

Второй запрос — с GET-параметром test_var и телом запроса (JSON).

curl http://localhost/cgi/test.sh?test_var=test -d '{"key1":"value1", "key2":"value2"}'

query string: test_var=test
server addr: 127.0.0.1
server port: 80
http host: localhost
http accept: */*
http Some-Field: 
Request body: {"key1":"value1", "key2":"value2"}

Как видно, скрипт получает GET-параметры запроса через переменную $QUERY_STRING, а также имеет доступ к телу запроса через STDIN (стандартный поток ввода). 

Посмотрим, как можно использовать более мощные скриптовые языки в режиме CGI на примере Perl.

Пример Perl-скрипта

Интерпретатор Perl, скорее всего, уже установлен в вашей системе. Если это не так, то можно его установить (пример для Ubuntu); также нам потребуется модуль CGI для работы с CGI‑параметрами. Установим всё одной командой:

apt install perl libcgi-pm-perl

Теперь создадим небольшой скрипт, который будет обращаться к параметрам запроса и показывать окружение. Разместим его рядом с Bash-скриптом (/var/www/html/cgi/test.pl). Не забываем про права доступа на чтение и запуск:

#!/usr/bin/perl

use v5.10;
use CGI;

my $cgi = CGI->new();
print $cgi->header;

my $test = $cgi->param('test');

print qq{
<!doctype html>
<html><h1>Test Perl content!</h1>
<p>Test param: $test</p>
<p>ENV UA: $ENV{HTTP_USER_AGENT}</p>
<p>Whole $ENV</p>};

foreach my $key (keys %ENV) {
	print "<p>ENV $key: $ENV{$key}</p>\n";
}

print qq{<html>};

В этом скрипте сначала устанавливается современный режим работы Perl (use v5.10) и подключается модуль CGI.pm (use CGI). Далее мы создаём объект $cgi для работы с параметрами запроса. Вообще говоря, модуль CGI.pm обладает широкой функциональностью, но его рекомендуется использовать только для работы с параметрами (GET и POST) и заголовками. Далее мы выводим корректный HTTP‑заголовок запроса (вызываем метод header объекта $cgi).

Следующее действие — заносим в переменную $test значение параметра test. Для демонстрации в браузере формируем HTML‑код страницы с выводом параметра и переменной окружения из хэша %ENV. Наконец, с помощью цикла foreach выводим все ключи и значения хэша окружения %ENV

Возможности Perl по обработке текстовых данных очень широки; также доступны сотни тысяч дополнительных модулей репозитория CPAN (Comprehensive Perl Archive Network) для готовых реализаций типичных задач (работа с базами данных, обработка файлов различных форматов, работа с изображениями, математические библиотеки и так далее).

Сообщение об ошибках модуля CGI для Angie оставляют желать лучшего. Например, при отсутствии в системе модуля CGI.pm мы получаем код ответа 500 (что нормально) и такое сообщение в логе ошибок: 

2025/11/24 16:34:35 [error] 4572#4572: *23 ngx_http_cgi_deref_process, status = 2 (11: Resource temporarily unavailable), client: 127.0.0.1, server: _, request: "GET /cgi/test.pl HTTP/1.1", host: "localhost"

Причина ошибки из этого сообщения совершенно не ясна. 

Дополнительные возможности модуля CGI

Выше мы разобрали подключение исполнения скриптов для целой локации. Иногда требуется скрыть адрес скрипта и ограничить возможности по запуску скриптов. В таком случае будет полезна директива cgi_pass. Например, можно реализовать рендеринг Markdown-документов. Конфигурация локации:

location ~ \.md$ {
  cgi_pass /var/www/html/cgi/render-markdown.sh;
}

Скрипт будет обрабатывать запросы, заканчивающиеся на .md, при этом пользователь будет обращаться к адресу .md‑файла, а не скрипта. Данные о запрашиваемом документе скрипт получит из переменных окружения. Пример такого скрипта:

#!/bin/sh

set -e

if [ ! -f "${DOCUMENT_ROOT}${PATH_INFO}" ]; then
    echo "Status: 404"
    echo
    exit
fi

echo "Status: 200"
echo "Content-Type: text/html"
echo

echo "<html><body>"
markdown "${DOCUMENT_ROOT}${PATH_INFO}"
echo "</body></html>"

При обращении к md‑файлам мы будем получать документ с рендерингом в HTML‑формате. 

Но что если потребуется исполнять скрипт от root‑пользователя? По умолчанию скрипты исполняются от пользователя, от имени которого запущены рабочие процессы Angie (например, www‑data или angie). Реализовать запуск от root можно с помощью механизма sudo (если он используется в вашей системе). Для этого создадим правило, разрешающее пользователю веб‑сервера (в нашем случае это www‑data) использовать sudo, для чего добавим запись в файл /etc/sudoers:

www-data ALL=(root) NOPASSWD: /var/www/html/bin/my-danger-script

Здесь мы разрешаем запускать от пользователя root только один скрипт (/var/www/html/bin/my-danger-script). Теперь внесём изменения в конфигурацию локации:

location /cgi/ {
  cgi on;
  cgi_interpreter /usr/bin/sudo;
}

В примере выше мы используем директиву cgi_interpreter для модификации способа запуска скрипта. Таким образом, скрипт будет запущен от пользователя root. Эту директиву можно использовать для запуска скриптов без бита исполнения, запускать бинарные файлы в окружении CGI, фильтровать вывод скриптов и так далее

Модуль CGI позволяет довольно подробно регулировать условия запуска скриптов, ограничивать скрипты с помощью chroot, настраивать окружение. Подробности можно посмотреть в документации модуля.

Итоги

Для решения специфических и точечных задач CGI-скрипты всё ещё могут быть полезны, и мы научились запускать их напрямую в Angie с использованием стороннего модуля. Мы также разобрали несколько примеров скриптов на Bash и Perl, где обсудили стандартные методы передачи данных в CGI.