Условные конструкции

Как настроить обработку HTTP-запросов по принципу «условие-действие».

Что это

Условные конструкции — это правила if-elseif-else, по которым Application Delivery Controller проверяет входящий запрос и выбирает, что c ним сделать.

Пример правила, которое можно настроить: если пришёл запрос GET /api/orders с заголовком Authorization, то добавить к нему служебный заголовок и отправить в нужный upstream; если такой же запрос пришёл без заголовка Authorization отклонить его с ошибкой 401.

Для чего можно задать условия

  • URI запроса;

  • полного request URI с query string;

  • HTTP-метода;

  • Host;

  • IP-адреса клиента;

  • схемы запроса: HTTP или HTTPS;

  • порта, на который пришёл запрос;

  • заголовков запроса;

  • query-параметров;

  • cookie;

  • любых доступных ngx.var переменных;

  • результата функции, например lower, len или active_members.

Какие действия можно выполнить

  • переписать URI запроса;

  • добавить или изменить заголовок запроса;

  • удалить заголовок запроса;

  • выбрать заранее созданный upstream_id;

  • вернуть HTTP-ответ клиенту от Application Delivery Controller без обращения к upstream;

  • отклонить запрос;

  • остановить дальнейшее выполнение правил или действий (actions).

Как всё работает

  1. Клиент отправляет запрос.

  2. Наша система получает запрос и сопоставляет его с Route, в котором настроен плагин logic-flow.

  3. Плагин читает список правил из rules.

  4. Для каждого правила плагин проверяет условие if.

    Условие строится по схеме: op + left + right. left обычно получает значение из запроса, например HTTP-метод или заголовок. right задаёт значение, с которым нужно сравнить left. op определяет способ сравнения: равно, содержит, начинается с префикса, подходит под регулярное выражение и т.д.

  5. Плагин выполняет действия (actions):

    • Если условие if выполняется, выполняются действия из then.

    • Если условие if не выполняется, проверяются блоки elseif: плагин проходит по ним сверху вниз и выполняет действия из первого встретившегося блока, условие которого оказалось истинным.

    • Если ни if, ни elseif не сработали, выполняются действия из else.

      Actions выполняются по порядку. Если action возвращает ответ клиенту через respond или reject, дальнейшая обработка запроса прекращается.

Какой плагин используется

logic-flow

Как подключить плагин

Через API

Плагин можно добавить к Route через Admin API. Для этого отправьте запрос на создание или обновление Route:

curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
  -H "X-API-KEY: ${admin_key}" \
  -d '{
    "id": "route-id",
    "uri": "/example",
    "plugins": {
      "logic-flow": {
        "...": "ваши настройки плагина"
      }
    },
    "upstream": {
      "...": "ваши настройки upstream"
    }
  }'

Здесь id — идентификатор Route, uri — путь, по которому запрос попадает в Route, plugins.logic-flow содержит конфигурацию плагина, а upstream — настройки бэкенд-серверов.

Через UI

  1. Откройте раздел Маршруты и нажмите + Добавить.

  2. В настройках маршрута (Route) перейдите в раздел Плагины .

  3. Нажмите + Добавить и выберите logic-flow.

  4. Укажите конфигурацию плагина как JSON-объект.

  5. Сохраните созданный маршрут (Route). Он будет добавлен с подключённым плагином.

Как настроить правила в плагине

Плагин настраивается через JSON DSL. Конфигурация задаётся объектом с массивом rules.

Каждое правило описывает условие и действия, которые нужно выполнить, если запрос соответствует этому условию.

Пример конфигурации:

{
  "rules": [
    {
      "if": {
        "op": "and",
        "conditions": [
          {
            "op": "equals",
            "left": { "var": "method" },
            "right": "GET"
          },
          {
            "op": "exists",
            "left": { "var": "header.Authorization" }
          }
        ]
      },
      "then": [
        {
          "action": "set_header",
          "name": "X-Logic-Flow",
          "value": "matched"
        }
      ],
      "elseif": [
        {
          "if": {
            "op": "and",
            "conditions": [
              {
                "op": "equals",
                "left": { "var": "method" },
                "right": "GET"
              },
              {
                "op": "not_exists",
                "left": { "var": "header.Authorization" }
              }
            ]
          },
          "then": [
            {
              "action": "reject",
              "status": 401,
              "body": "authorization required"
            }
          ]
        }
      ],
      "else": [
        {
          "action": "reject",
          "status": 403,
          "body": "logic-flow rejected"
        }
      ]
    }
  ]
}

Эта конфигурация читается так:

  • Если HTTP-метод запроса равен GET и в запросе есть заголовок Authorization, плагин добавит или изменит заголовок X-Logic-Flow.

  • Если HTTP-метод запроса равен GET, но заголовка Authorization нет, плагин вернёт ответ 401.

  • Во всех остальных случаях плагин вернёт ответ 403.

Плагин проверяет ветки правила сверху вниз:

  1. Сначала проверяется условие в if.

  2. Если if не сработал, проверяются ветки elseif — в том порядке, в котором они указаны.

  3. Если не сработали ни if, ни elseif, выполняется ветка else.

Выполняется только одна ветка правила. Actions внутри выбранной ветки выполняются последовательно — в порядке, в котором они указаны в массиве.

Ниже описываем, как устроены основные поля конфигурации: rules, if, then, elseif и else, а также приводим полные списки операторов, переменных, функций и actions.

rules

rules — массив правил.

{
  "rules": [
    {
      "if": {},
      "then": [],
      "elseif": [],
      "else": []
    }
  ]
}

Каждый объект внутри rules — отдельное правило. Внутри правила можно задать:

Поле Что задаёт

if

Основное условие правила.

then

Actions, которые выполняются, если if истинно. Полный список actions смотрите в разделе Actions.

elseif

Дополнительные условные ветки.

else

Actions по умолчанию, если не сработали if и elseif. Полный список actions смотрите в разделе Actions.

if

if задаёт условие, которое плагин проверяет у запроса.

В простом условии обычно используются три поля:

{
  "op": "equals",
  "left": { "var": "method" },
  "right": "GET"
}
Поле Что задаёт

op

Оператор проверки: что именно нужно сделать со значениями — например, сравнить, проверить наличие или применить регулярное выражение. Полный список операторов смотрите в разделе Операторы.

left

Значение, которое нужно проверить. Обычно это данные из запроса: метод, URI, заголовок, query-параметр или cookie. Полный список переменных смотрите в разделе Переменные.

right

Значение, с которым сравнивается left. Обычно строка, число или другое ожидаемое значение.

Например, условие выше читается так: взять HTTP-метод запроса и проверить, равен ли он GET.

В left и right можно использовать:

  • переменные — чтобы взять значение из запроса. Полный список переменных смотрите в разделе Переменные;

  • функции — чтобы предварительно обработать значение. Полный список функций смотрите в разделе Функции;

  • литералы — строки, числа и другие фиксированные значения.

Сложные условия

Для сложных условий используются операторы and и or. Вместо left и right в них передаётся массив conditions:

{
  "op": "and",
  "conditions": [
    {
      "op": "starts_with",
      "left": { "var": "uri" },
      "right": "/api/"
    },
    {
      "op": "equals",
      "left": { "var": "method" },
      "right": "GET"
    }
  ]
}

Такое условие читается так: URI должен начинаться с /api/, а метод должен быть равен GET.

then

then — массив действий (actions), которые выполняются, если условие if истинно.

"then": [
  {
    "action": "rewrite_uri",
    "from": "/api/orders/",
    "to": "/internal/orders/"
  },
  {
    "action": "set_header",
    "name": "X-Logic-Flow",
    "value": "matched"
  }
]

Каждый объект внутри then — отдельный action. Полный список возможных actions смотрите в разделе Actions.

Actions выполняются последовательно. Если порядок действий важен, указывайте их в нужном порядке.

elseif

elseif — массив дополнительных веток условия.

Каждая ветка elseif состоит из собственного if и собственного then. В if используются условия, операторы, переменные и функции, а в then — actions.

"elseif": [
  {
    "if": {
      "op": "not_exists",
      "left": { "var": "header.Authorization" }
    },
    "then": [
      {
        "action": "reject",
        "status": 401,
        "body": "authorization required"
      }
    ]
  }
]

Ветки elseif проверяются только в том случае, если основное условие if не выполнилось.

Если одна из веток elseif сработала, остальные ветки не проверяются.

Полный список возможных операторов, переменных, функций и actions смотрите в Справочнике.

else

else — массив действий по умолчанию.

"else": [
  {
    "action": "reject",
    "status": 403,
    "body": "logic-flow rejected"
  }
]

else выполняется, если не сработали ни основное условие if, ни одна из веток elseif.

Полный список возможных actions смотрите в разделе Actions.

Справочник

Операторы

Оператор Что проверяет Пример

equals

Значения равны

method == GET

not_equals

Значения не равны

method != POST

starts_with

Значение начинается с указанного префикса

uri начинается с /api/

ends_with

Значение заканчивается указанным суффиксом

uri заканчивается на .json

contains

Значение содержит подстроку

User-Agent содержит curl

regex

Значение соответствует регулярному выражению

^/api/v[0-9]+/

gt, lt

Числовое сравнение

len(uri) > 10

gte, lte

Числовое сравнение с учётом равенства

total_count >= 1

exists

Значение существует и не пустое

header.Authorization

not_exists

Значение отсутствует или пустое

arg.token

and

Все вложенные условия истинны

URI начинается с /api/ и метод равен GET

or

Хотя бы одно вложенное условие истинно

Метод равен GET или POST

not

Инвертирует условие

Не DELETE

Переменные

Переменные позволяют брать значения из запроса.

Переменная Что возвращает

uri

Нормализованный URI запроса, например /api/test.

request_uri

Полный request URI вместе с query string.

method

HTTP-метод запроса.

host

Значение Host из запроса.

remote_addr

IP-адрес клиента с точки зрения ADC.

scheme

Схему запроса: http или https.

server_port

Порт, на который пришёл запрос.

header.<Header-Name>

Значение request header.

arg.<name>

Значение query-параметра.

cookie.<name>

Значение cookie.

Любая ngx.var -переменная

Значение ngx.var[name], если переменная не описана явно.

Примеры:

{ "var": "method" }
{ "var": "uri" }
{ "var": "header.Authorization" }
{ "var": "arg.token" }
{ "var": "cookie.session_id" }

Функции

Функции можно использовать внутри left или right. Они получают аргументы через args, обрабатывают их и возвращают значение для сравнения.

Функция Что делает

lower(value)

Приводит строку к нижнему регистру.

upper(value)

Приводит строку к верхнему регистру.

len(value)

Возвращает длину строкового представления значения.

replace(source, from, to)

Заменяет подстроку в строке.

active_members(upstream_id)

Возвращает количество healthy nodes upstream из кеша healthcheck.

upstream_healthy_count(upstream_id)

Алиас для active_members.

upstream_total_count(upstream_id)

Возвращает total_count nodes upstream из кеша.

Пример:

{
  "op": "equals",
  "left": {
    "func": "lower",
    "args": [
      { "var": "header.X-Mode" }
    ]
  },
  "right": "debug"
}

Условие берёт значение заголовка X-Mode, приводит его к нижнему регистру и сравнивает со строкой debug.

Actions

Actions описывают, что плагин должен сделать после успешной проверки условия.

Action Что делает

rewrite_uri

Переписывает URI запроса через value или через пару from / to.

set_header

Добавляет или изменяет request header перед отправкой запроса в upstream.

remove_header

Удаляет request header.

set_upstream_id

Выбирает заранее созданный upstream по его upstream_id.

respond

Возвращает HTTP-ответ из ADC без обращения к upstream.

reject

Возвращает отказ. По умолчанию использует статус 403.

stop

Останавливает дальнейшее выполнение rules/actions.

Что учесть

then, elseif.then и else всегда задаются массивами actions. Даже если действие одно, его нужно передавать внутри массива.

Actions выполняются последовательно. Если action возвращает ответ клиенту через respond или reject, дальнейшая обработка запроса прекращается.

stop останавливает дальнейшее выполнение rules/actions, но сам по себе не возвращает клиенту HTTP-ответ.

set_upstream_id должен ссылаться на заранее созданный upstream.

Плагин работает с обработкой запроса. Изменение response body и response headers пока не реализовано.

Пример настройки плагина 1

В этом примере запрос попадает в Route, который должен отправить его в upstream abc_pool. Но перед передачей запроса система проверяет по данным healthcheck, есть ли в abc_pool «живые» бэкенды:

  • если нет доступных бэкендов, система не отправляет запрос в upstream, а сразу возвращает клиенту ответ с HTTP-кодом 503 и кастомным телом;

  • если в abc_pool есть доступные бэкенды, система отправляет запрос в этот upstream и добавляет к запросу кастомный заголовок.

Настройка плагина logic-flow:

{
  "rules": [
    {
      "if": {
        "op": "equals",
        "left": {
          "func": "active_members",
          "args": ["abc_pool"]
        },
        "right": 0
      },
      "then": [
        {
          "action": "respond",
          "status": 503,
          "body": "abc_pool is DOWN"
        }
      ],
      "else": [
        {
          "action": "set_upstream_id",
          "upstream_id": "abc_pool"
        },
        {
          "action": "set_header",
          "name": "X-Logic-Flow-Upstream-State",
          "value": "abc_pool_has_active_members"
        }
      ]
    }
  ]
}

Здесь active_members возвращает количество healthy nodes в upstream abc_pool. Условие equals сравнивает это значение с 0. Если живых бэкендов нет, срабатывает then и система возвращает 503. Если живые бэкенды есть, срабатывает else: запрос отправляется в abc_pool, а перед отправкой добавляется заголовок X-Logic-Flow-Upstream-State.

Пример настройки плагина 2

В этом примере система проверяет запросы к /api/orders:

  • если запрос пришёл методом GET, начинается с /api/orders и содержит заголовок Authorization, система переписывает URI, добавляет служебный заголовок и отправляет запрос в upstream orders_pool;

  • если заголовка Authorization нет, система возвращает 401;

  • во всех остальных случаях система возвращает 405.

Исходный запрос клиента:

GET /api/orders/ORD-1001
Authorization: Bearer token

Настройка плагина logic-flow:

{
  "rules": [
    {
      "if": {
        "op": "and",
        "conditions": [
          {
            "op": "starts_with",
            "left": { "var": "uri" },
            "right": "/api/orders"
          },
          {
            "op": "equals",
            "left": { "var": "method" },
            "right": "GET"
          },
          {
            "op": "exists",
            "left": { "var": "header.Authorization" }
          }
        ]
      },
      "then": [
        {
          "action": "rewrite_uri",
          "from": "/api/orders/",
          "to": "/internal/orders/"
        },
        {
          "action": "set_header",
          "name": "X-Logic-Flow",
          "value": "orders-api"
        },
        {
          "action": "set_upstream_id",
          "upstream_id": "orders_pool"
        }
      ],
      "elseif": [
        {
          "if": {
            "op": "and",
            "conditions": [
              {
                "op": "starts_with",
                "left": { "var": "uri" },
                "right": "/api/orders"
              },
              {
                "op": "equals",
                "left": { "var": "method" },
                "right": "GET"
              },
              {
                "op": "not_exists",
                "left": { "var": "header.Authorization" }
              }
            ]
          },
          "then": [
            {
              "action": "reject",
              "status": 401,
              "body": "authorization required"
            }
          ]
        }
      ],
      "else": [
        {
          "action": "reject",
          "status": 405,
          "body": "method not allowed"
        }
      ]
    }
  ]
}

После обработки upstream получит запрос с переписанным URI и добавленным заголовком:

GET /internal/orders/ORD-1001
X-Logic-Flow: orders-api
Authorization: Bearer token

Если клиент отправит запрос без Authorization, система не передаст его на upstream и вернёт ответ:

HTTP/1.1 401 Unauthorized

authorization required

Здесь if задаёт основное условие, then описывает действия для подходящего запроса, elseif обрабатывает запрос без авторизации, а else возвращает ошибку для остальных случаев.