Примечание 1: при написании оригинала этого поста искусственный интеллект не пострадал.
Примечание 2: этот перевод блог-поста с blog.nginx.com выполнен ИИ, потом вычитан и отредактирован лично автором.
Введение
В сентябре 2026 года мы выпустили NGINX 1.31.5 с несколькими ключевыми фичами, объединёнными одной целью. Мы расширили базовые методы маршрутизации и самые важные директивы nginx, чтобы обеспечить прямую, не требующую скриптов маршрутизацию любого API-трафика. В этом посте мы разберём самую важную возможность, которую мы назвали «предикатные локейшены» (predicate locations), и объясним, как она сочетается с остальными улучшениями, которые мы делаем для поддержки современных приложений.
Проблема
В идеальном мире HTTP — отличный протокол. При правильном использовании он обеспечивает крайне быструю и надёжную коммуникацию, которая одновременно читаема человеком и программируема для клиентов и серверов.
HTTP-взаимодействие начинается со строки запроса вроде такой:
POST /api/v1/coffee HTTP/1.1
Метод (в примере — POST) предназначен для того, чтобы сообщить бэкенд-серверу, какое действие нужно выполнить.
URL (/api/v1/coffee) предназначен для указания на ресурс.
Заголовки, следующие за строкой запроса, предназначены для того, чтобы инструктировать сервер по различным деталям операций. Тело запроса содержит полезную нагрузку.
Маршрутизация HTTP-запросов обычно строится вокруг URL, а в некоторых случаях — вокруг методов.
Разработчики приложений игнорируют соглашения протокола и размещают модификаторы трафика внутри заголовков и тела запроса.
В результате веб-серверы, прокси, устройства безопасности и балансировщики нагрузки с трудом справляются с эффективной маршрутизацией трафика. Они не рассчитаны на то, что данные приложения окажутся не в тех местах.
Модернизация NGINX: предикатные локейшены
NGINX, как и любой другой промежуточный прокси, проектировался вокруг URL.
В NGINX 1.31.5 мы сняли это ограничение и позволили любой переменной становиться модификатором трафика для любого блока location в конфигурации.
Классические блоки location
Структура конфигурации NGINX в значительной степени построена на блоках location. В качестве иллюстративного (не буквального) примера посмотрите на такую структуру:
http { server { location / { ... } location /images { ... } location /api { ... } location /api/v1/public { ... } location /api/v1/configuration { ... } location /api/v1/coffee { ... } }}
Такая структура позволяет размещать наиболее значимые и «рабочие» директивы в этих блоках location. Вы можете назначать разные пулы бэкенд-серверов (upstreams) для маршрутизации, настраивать различные уровни лимитов по скорости и количеству запросов, изменять заголовки в обоих направлениях, настраивать аутентификацию, WAF и другие средства безопасности — и всё это независимо для каждого location.
Мы не можем переопределить эту базовую структуру. Она слишком мощная, стабильная и гибко настраиваемая. Большинству пользователей NGINX она нравится.
Предикатные блоки location
С расширением locations до поддержки любых переменных типичная структура локейшенов становится более продвинутой. Например:
http { server { location / { ... } location /images { ... } location $post_requests { ... } location $bot_traffic { ... } location $internal_tests { ... } location $ai_mcp_queries { ... } }}
NGINX вычисляет переменные. Когда переменная оказывается истинной (точнее, «чем угодно, кроме пустой строки или нуля»), вы попадаете в этот location.
Дальше делайте с этим трафиком всё, что хотите.
Маппинг предикатов
В NGINX есть хорошо известный модуль map. Отображение переменных в значения становится критически важным для предикатной логики.
Допустим, у вас есть список HTTP-методов, которые вы хотите считать «истинными» для использования в предикатном локейшене. Все остальные методы должны быть ложными. Используйте такой простой пример в вашей конфигурации:
map $request_method $restricted_methods { POST 1; PUT 1; DELETE 1; default 0;}
Устанавливать значение по умолчанию в «0» не обязательно — по умолчанию всегда "", что вычисляется как false. Однако явное объявление может быть полезно при отладке.
В этом примере вы теперь можете использовать переменную $restricted_methods в других местах конфигурации, в том числе в предикатных локейшенах.
Вложенность предикатов (nesting)
Конфигурация, полная предикатов, может стать довольно громоздкой.
Если вычисление предиката сложное по своей природе — например, если он читает тело запроса, — производительность может упасть.
По этим причинам делайте структуру локейшенов такой, чтобы она соответствовала вашему приложению.
Предикатные локейшены поддерживают вложенность вместе с классическими локейшенами на основе URL. Мы не предписываем, как именно вы должны их вкладывать. У вашего приложения свои паттерны трафика, которые и подскажут разумный вариант.
В примере ниже мы сначала используем URI, а дальнейшее разделение трафика делаем уже по предикатам:
http { server { location /images { ... } location /api/v1 { location $restricted_requests { location $post_requests { ... } location $bot_traffic { ... } } location $internal_tests { ... } location $ai_mcp_queries { ... } } }}
Как альтернативный подход, вы можете сначала определить предикаты, а обычные локейшены разместить на следующем уровне:
http { server { location $public_methods { ... } location $restricted_methods { location /api/v1/private { location $internal_ips { ... } location $bot_traffic { ... } } location /api { ... } location /wp-admin { ... } } }}
Маршрутизация по телу HTTP-запроса и значениям из JSON
Предикаты изначально спроектированы так, чтобы работать с другими новыми возможностями NGINX — ранним чтением тела HTTP-запроса и встроенным разбором JSON.
JSON — стандартный формат для современных API. JSONPath — стандарт для поиска значений.
Теперь вы можете задавать переменные на основе отдельных полей и использовать их как предикаты в локейшенах.
Для примера давайте маршрутизировать трафик на основе единицы или нуля в поле «vip» вот в таком теле JSON:
{ "order_id": "1042", "item": "latte", "size": "medium", "user": { "name": "Nick", "params": { "vip": 1 } }}
Мы используем следующую конфигурацию NGINX:
events { }http { client_body_early_read on; # Необходимо включить для раннего чтения тела json_set $vip $request_body user.params.vip; server { location $vip { return 200 "VIP user\n"; } location / { return 200 "Regular user, applying restrictions\n"; # limit_req ...; } }}
Не забудьте включить раннее чтение тела запроса с помощью client_body_early_read.
Протестируем с помощью curl:
nick:~$ curl -X POST -d '{"order_id":"1042","item":"latte","size":"medium","user":{"name":"Somebody","params":{"vip":0}}}' 127.1:8085Regular user, applying restrictionsnick:~$ curl -X POST -d '{"order_id":"1042","item":"latte","size":"medium","user":{"name":"Nick","params":{"vip":1}}}' 127.1:8085VIP user
Эта конфигурация демонстрирует то, чего раньше не было: простоту. Мы использовали стандартные для индустрии инструменты, создали переменную и напрямую по ней маршрутизировали.
Разбор JSON и установка переменных не ограничены телом запроса. Вы можете взять любую переменную NGINX, например HTTP-заголовок или JWT, в качестве источника и распарсить её.
Что ещё можно сделать с этим примером маршрутизации? Отправить трафик на разные upstream-серверы, ограничить его по скорости, количеству запросов или IP-адресам. Изменить заголовки и полезную нагрузку. Применить ограничения на основе содержимого, включить или отключить application firewall. Весь набор возможностей NGINX теперь доступен через предельно простой интерфейс.
Сложная маршрутизация с NJS
NJS умеет выполнять множество функций. Он может полностью взять на себя слой маршрутизации NGINX. Однако если злоупотреблять NJS в конфигурации NGINX, она может стать громоздкой. Кроме того, до появления предикатов было сложно «выйти» из NJS обратно в локейшены NGINX. Нашим пользователям приходилось писать мегабайты кода на NJS только ради маршрутизации.
С предикатами NJS упрощается.
В следующем примере мы подсчитаем количество слов в теле HTTP-запроса, примем решение, не слишком ли длинный запрос, и по-разному направим трафик на основе этого решения. Мы используем директиву client_body_early_read, чтобы переменная $request_body создавалась на раннем этапе обработки, применим JavaScript для подсчёта слов и установим предикат $many_words. Затем используем его в директиве location.
JavaScript-файл http.js:
function counter(r) { const words = r.variables['request_body'].trim().split(/\s+/).length; if (words > 10) { return 1; } return 0;}export default {counter};
Конфигурационный файл NGINX nginx.conf:
events { }http { client_body_early_read on; js_import /path/to/conf/http.js; js_engine qjs; js_set $many_tokens http.counter; server { location $many_tokens { return 413 "Request body has too many tokens\n"; } location / { return 200 "Request is small, OK\n"; } }}
Тестирование с помощью curl:
nick:~$ curl -X POST -d "a b c d e f g h j k l" 127.1:80Request body has too many tokensnick:~$ curl -X POST -d "a b c d" 127.1:80Request is small, OK
Как видите, мы избежали создания сложного набора редиректов и именованных локейшенов, штатно задействовали все возможности nginx через прямое использование директив location и применили всю мощь JavaScript для конкретной небольшой задачи.
Будущее: сложные условия
Мы работаем над добавлением встроенной поддержки сложной условной логики внутри NGINX. Цель — упростить обработку разнообразных сценариев трафика с точностью и эффективностью, сохраняя конфигурацию nginx простой и надёжной.
Вы сможете задавать сложные логические условия, очень похожие на текущую директиву «if», но без её оговорок и ограничений.
Эта возможность будет доступна в виде отдельной директивы, которая вычисляется в true/false в зависимости от условия. Вы сможете использовать её в предикатных локейшенах или в других местах, где нужна логика true/false.
Советы, приёмы и подводные камни
Если ваша конфигурация насыщена предикатами, используйте следующие рекомендации:
-
Создавайте catch-all локейшены. В стандартных конфигурациях обычный блок «location /» имеет смысл только на верхнем уровне вложенности. С предикатами он имеет смысл на любом уровне вложенности как fallback/catch-all. Размещайте его в конце блока.
-
Ограничивайте раннее чтение тела запроса только теми участками, где это действительно нужно. Большие тела запросов требуют много памяти и CPU для обработки. Директива
client_body_early_readподдерживает переменные. Используйте предикаты по HTTP-заголовкам или URL, чтобы включать её выборочно. Конечно, когда это возможно. -
Обращайте особое внимание на порядок предикатов в конфигурации. Из соображений производительности размещайте простые и часто срабатывающие предикатные локейшены вверху списка, а более сложные — ближе к низу.
-
Называйте предикаты осмысленно, чтобы позже вы могли эффективно их отслеживать и отлаживать.
-
Используйте специальные форматы логов для отслеживания переменных. Ваши логи могут показывать значения нескольких переменных в одной строке. Это поможет при отладке.
Заключение
Теперь NGINX позволяет маршрутизировать трафик практически по чему угодно. Новые фичи гармонично работают вместе:
-
предикатные локейшены;
-
раннее чтение тела запроса;
-
встроенный разбор JSON.
Все эти возможности — строительные блоки. Они бесшовно работают с остальной частью NGINX.
Мы не предписываем, как должно выглядеть ваше приложение, как организовать конфигурацию и как ею управлять. С помощью этих универсальных функций и примеров вы можете создать собственный прокси и балансировщик нагрузки, подходящий практически под любой паттерн трафика.
Смотрите справочную документацию на nginx.org:
-
NGINX Location Blocks: https://nginx.org/en/docs/http/ngx_http_core_module.html#location
-
NGINX JSON Module: https://nginx.org/en/docs/http/ngx_http_json_module.html
-
NGINX client_body_early_read Directive: https://nginx.org/en/docs/http/ngx_http_core_module.html#client_body_early_read
-
NGINX 1.31.5 Release Blog: https://blog.nginx.org/blog/nginx-1-31-5-control-api-predicate-locations-early-body-inspection-and-more
-
Change Log for NGINX: https://nginx.org/en/CHANGES
ссылка на оригинал статьи https://habr.com/ru/articles/1080058/