Роутинг NGINX на предикатах для обработки API-трафика без скриптов

от автора

Примечание 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:

ссылка на оригинал статьи https://habr.com/ru/articles/1080058/