Переменные в сервере Angie используются для расширенного управления обработкой запросов, более гибких конфигураций, а также получения подробной информации о клиентах и запросах. Постараемся разобраться со всем спектром возможностей переменных в Angie.

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

  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.

  21. Защита от DoS‑атак в Angie стандартными модулями.

  22. Защита от DoS‑атак в Angie (дополнительные средства).

  23. Автоматические TLS‑сертификаты в Angie с модулем ACME.

  24. HTTP/2 и HTTP/3: настройка, достоинства и недостатки.

  25. Работа с картинками в Angie.

  26. Инструменты для бенчмарка веб‑сервера.

  27. Тест современных компрессоров для HTTP.

  28. Визуализация кастомных метрик Angie в Grafana.

  29. Влияние TLS‑библиотек и настроек на производительность.

  30. Сборка Angie из исходных кодов.

  31. Оптимизация Angie для высоких нагрузок.

  32. Логирование в Angie.

  33. Работа с переменными в Angie.

Видеоверсия

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

Как работают переменные

Переменные в Angie во многом схожи с переменными в скриптовых языках. С их помощью можно реализовать конфигурацию с элементами программного кода и конструкций if-then.  Переменные объявляются на этапе конфигурации, содержат строки и устанавливаются для каждого запроса. Область видимости — глобальная в рамках запроса. Все переменные можно разделить на три типа:

  • встроенные переменные — определяются модулями Angie, описаны в справочнике директив;

  • пользовательские переменные — определяются в конфигурации через директивы map, set, geo, split_clients.

  • позиционные переменные — определяются в случае совпадения регулярного выражения ($1, $2 и т.д.)

При этом, значения переменных вычисляются только в момент использования, поэтому сам факт определения пользовательских переменных через map или geo не добавляет накладных расходов на обработку запроса, если они не используются.

Отдельно можно выделить группы переменных, у которых фиксирована часть имени, а другая определяется свойствами запроса, например: $arg_*, $http_* и т.д.

Обращение к переменным всегда происходит со знаком доллара: $uri, $host и так далее.

Сразу стоит сказать, что экранирование знака доллара в значении переменной не поддерживается. Для использования знака доллара в строках с переменными нужно использовать директиву geo, для получения переменной с нужным значением:

http {
  geo $doll {
		default "$";
  }
}

Переменные в директивах

По умолчанию переменные нельзя указывать в качестве значений директив. Все случаи, где использование переменных разрешено, описаны в документации директив. Например, рассмотрим директиву add_header в HTTP. Для отладки при работе с переменными можно вывести значение строки с переменной:

server {
add_header x-ruri "request_uri: $request_uri";
add_header x-uri "uri: $uri";
}

curl --head http://locahost?ddd

...
x-ruri: request_uri: /?ddd
x-uri: uri: /
...

Как видно из примера, допустимы смешанные строки состоящие из переменных и обычных символов. Если необходимо отделить имя переменной от обычной строки, можно использовать фигурные скобки:

${uri}_test

Одним из самых мощных инструментов работы с переменными является модуль Map, к которому мы переходим далее.

Определение переменных через map

Встроенный модуль Map позволяет создавать пользовательские переменные на основе других переменных и строк. Например, нужно получить переменную с признаком “бот” в зависимости от HTTP-заголовка User-agent:

http {
    map $http_user_agent $limit_search_bots {
		default 0;
		~*(google|wget|bing|yandex|msnbot|apachebench|curl) 1;
		"bot" 2;
	}
}

Обратите внимание, что использование map допустимо только в контекстах http или stream. Директива map имеет два параметра: строка с исходными переменными ($http_user_agent) и результирующая переменная ($limit_search_bots). В блоке определяются различные значения результирующей переменной. Значение default (0) будет установлено, если нет ни одного совпадения с исходными параметрами в других вариантах.

Вторая строка блока представляет собой регулярное выражение без учета регистра (оператор ~*), с которым будет происходить сопоставление строки-переменной $http_user_agent. В случае совпадения с регулярным выражением переменная $limit_search_bots примет значение 1

Третья строка это простое сравнение строки (bot) со значением переменной $http_user_agent без учёта регистра. В случае совпадения  переменная $limit_search_bots примет значение 2.

Директива map лишь определяет переменную. Использовать результирующую переменную можно при записи в лог (указать в формате или реализовать условное логирование), в значении других директив, а также применить в блоке if модуля Rewrite. Рассмотрим последний вариант: 

server {
  if ($limit_search_bots = 1) {
  	return 421;
  }
}

Здесь блок if имеет сравнение переменной $limit_search_bots с единицей. В случае успешного сравнения применяется конфигурация в блоке, то есть возврат кода ответа 421. Внутри блока рекомендуется использовать только директивы модуля Rewrite для сохранения предсказуемого поведения конфигурации сервера. Например, можно заменить return на перенаправление через rewrite или отправить обработку в именованную локацию через goto.

Директива map поддерживает подключение списков значений из внешних файлов через параметр include, что позволяет улучшить читаемость конфигурации с большими списками:

map $uri $redir {
    default     "";
    include     /etc/angie/redir.map;
}

# cat /etc/angie/redir.map

...
/old_content                   /new-page;
/legacy/api            /new/api;
"~^/old_blog/([0-9]+)/(.*)$"   /blog/$1/$2;
...

server {
  if ($redir) {
  	return 301 $redir;
  }
}

В этом примере мы определили список адресов для перенаправления и целевые адреса для них. Если совпадения по $uri в списке нет, то переменная получает значение пустой строки и перенаправление не происходит.

Еще одной опцией map является свойство volatile.  При активации этой опции, переменная не будет кэшироваться на время запроса, а будет вычисляться каждый раз при обращении к ней. Такое поведение может быть полезно, если вы изменяете переменную в процессе обработки запроса и не хотите допускать кэширование значения. Например, некоторые встроенные переменные допускают изменение и следующая конструкция будет иметь смысл:

http {
  map $uri $is_test {
    volatile;
    default 0;
    "/test/" 1;
    "/ddd" 2;
  }

  server {
    location /test/ {
      rewrite ^ /ddd?$is_test last;
      add_header x-test "$is_test";
      proxy_pass http://localhost:8080;
    }
  
    location / {
      add_header x-test-root "$is_test";
      proxy_pass http://localhost:8080;
    }
  }
}

Сначала мы определили переменную $is_test через map с атрибутом volatile. Она зависит от обработанного адреса страницы ($uri). Запрос, попадая в локацию /test/, перенаправляется в location /. При этом значение переменной $is_test в первом случае (в директиве rewrite) будет “1”, а во втором при выводе заголовка уже “2”.  То есть, значение переменной изменяется в процессе выполнения одного запроса.

Определение адреса проксируемого сервера через переменные

В случае с проксированием есть возможность определить адрес или группу серверов через переменную. Для этого создадим переменную:

http {
    map $uri $host_dest {
		default back_def;
		"~^/admin/" back_adm;
		"~^/api/" back_api;
	}
}

Теперь у нас есть переменная $host_dest, которая содержит название группы серверов для проксирования. Применить эту переменную несложно:

http {
  upstream back_def {
  	server 192.168.0.100;
  }
  upstream back_adm {
  	server 192.168.0.110;
  }
  upstream back_api {
  	server 192.168.0.120;
  }
  
  server {
  	location / {
    	proxy_pass $host_dest;
    }
  }
}

Приведём пример для контекста stream с модулем SSL_preread, где с использованием директивы ssl_preread можно получить версию протокола TLS и реализовать проксирование нескольких протоколов с помощью одного TCP-порта, например, так:

stream {
  map $ssl_preread_protocol $upstream {
    ""        192.168.122.142:22;
    "TLSv1.2" 192.168.122.142:443;
    "TLSv1.3" 192.168.122.142:443;
    default   192.168.122.142:443;
  }
  
  server {
  	listen 8443;
  	ssl_preread on;
  	proxy_pass $upstream;
  }
}

При наличии валидных значений версии протокола TLS проксирование производится по адресу 192.168.122.142:443, а для пустого значения версии адрес 192.168.122.142:22 (SSH).

Использование модуля и директивы geo

Модуль geo решает простую задачу: установить значение переменной в зависимости от IP-адреса клиента. Например, с помощью geo можно реализовать простые правила доступа к веб-приложению.

http {
  geo $block_access {
      default        0;
      1.2.3.4        1;
      196.186.1.0/24 1;
      200.1.0.0/16   1;
      2001:0db8::/32 1;
  }
  
  server {
  	if ($block_access) {
  		return 403;
    }
  }
}

По умолчанию адрес клиента берётся из переменной $remote_addr, но можно задать произвольную переменную.

http {
  geo $http_x_forwarded_for $block_access {
      default        0;
      1.2.3.4        1;
      196.186.1.0/24 1;
      200.1.0.0/16   1;
      2001:0db8::/32 1;
  }
}

Итоги

Мы разобрали множество способов применения переменных в Angie. Они позволяют создать гибкие конфигурации и частично кастомизировать поведение сервера. При грамотном использовании переменные решают широкий спектр задач для веб-сервера и балансировщика.