Условные конструкции
Как настроить обработку 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).
Как всё работает
-
Клиент отправляет запрос.
-
Наша система получает запрос и сопоставляет его с Route, в котором настроен плагин
logic-flow. -
Плагин читает список правил из
rules. -
Для каждого правила плагин проверяет условие
if.Условие строится по схеме:
op+left+right.leftобычно получает значение из запроса, например HTTP-метод или заголовок.rightзадаёт значение, с которым нужно сравнитьleft.opопределяет способ сравнения: равно, содержит, начинается с префикса, подходит под регулярное выражение и т.д. -
Плагин выполняет действия (actions):
-
Если условие
ifвыполняется, выполняются действия изthen. -
Если условие
ifне выполняется, проверяются блокиelseif: плагин проходит по ним сверху вниз и выполняет действия из первого встретившегося блока, условие которого оказалось истинным. -
Если ни
if, ниelseifне сработали, выполняются действия изelse.Actions выполняются по порядку. Если action возвращает ответ клиенту через
respondилиreject, дальнейшая обработка запроса прекращается.
-
Как подключить плагин
Через 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 — настройки бэкенд-серверов.
Как настроить правила в плагине
Плагин настраивается через 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.
Плагин проверяет ветки правила сверху вниз:
-
Сначала проверяется условие в
if. -
Если
ifне сработал, проверяются веткиelseif— в том порядке, в котором они указаны. -
Если не сработали ни
if, ниelseif, выполняется веткаelse.
Выполняется только одна ветка правила. Actions внутри выбранной ветки выполняются последовательно — в порядке, в котором они указаны в массиве.
Ниже описываем, как устроены основные поля конфигурации: rules, if, then, elseif и else, а также приводим полные списки операторов, переменных, функций и actions.
rules
rules — массив правил.
{
"rules": [
{
"if": {},
"then": [],
"elseif": [],
"else": []
}
]
}
Каждый объект внутри rules — отдельное правило. Внутри правила можно задать:
| Поле | Что задаёт |
|---|---|
|
Основное условие правила. |
|
Actions, которые выполняются, если |
|
Дополнительные условные ветки. |
|
Actions по умолчанию, если не сработали |
if
if задаёт условие, которое плагин проверяет у запроса.
В простом условии обычно используются три поля:
{
"op": "equals",
"left": { "var": "method" },
"right": "GET"
}
| Поле | Что задаёт |
|---|---|
|
Оператор проверки: что именно нужно сделать со значениями — например, сравнить, проверить наличие или применить регулярное выражение. Полный список операторов смотрите в разделе Операторы. |
|
Значение, которое нужно проверить. Обычно это данные из запроса: метод, URI, заголовок, query-параметр или cookie. Полный список переменных смотрите в разделе Переменные. |
|
Значение, с которым сравнивается |
Например, условие выше читается так: взять 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.
Справочник
Операторы
| Оператор | Что проверяет | Пример |
|---|---|---|
|
Значения равны |
|
|
Значения не равны |
|
|
Значение начинается с указанного префикса |
|
|
Значение заканчивается указанным суффиксом |
|
|
Значение содержит подстроку |
|
|
Значение соответствует регулярному выражению |
|
|
Числовое сравнение |
|
|
Числовое сравнение с учётом равенства |
|
|
Значение существует и не пустое |
|
|
Значение отсутствует или пустое |
|
|
Все вложенные условия истинны |
URI начинается с |
|
Хотя бы одно вложенное условие истинно |
Метод равен |
|
Инвертирует условие |
Не |
Переменные
Переменные позволяют брать значения из запроса.
| Переменная | Что возвращает |
|---|---|
|
Нормализованный URI запроса, например |
|
Полный request URI вместе с query string. |
|
HTTP-метод запроса. |
|
Значение |
|
IP-адрес клиента с точки зрения ADC. |
|
Схему запроса: |
|
Порт, на который пришёл запрос. |
|
Значение request header. |
|
Значение query-параметра. |
|
Значение cookie. |
Любая |
Значение |
Примеры:
{ "var": "method" }
{ "var": "uri" }
{ "var": "header.Authorization" }
{ "var": "arg.token" }
{ "var": "cookie.session_id" }
Функции
Функции можно использовать внутри left или right. Они получают аргументы через args, обрабатывают их и возвращают значение для сравнения.
| Функция | Что делает |
|---|---|
|
Приводит строку к нижнему регистру. |
|
Приводит строку к верхнему регистру. |
|
Возвращает длину строкового представления значения. |
|
Заменяет подстроку в строке. |
|
Возвращает количество healthy nodes upstream из кеша healthcheck. |
|
Алиас для |
|
Возвращает |
Пример:
{
"op": "equals",
"left": {
"func": "lower",
"args": [
{ "var": "header.X-Mode" }
]
},
"right": "debug"
}
Условие берёт значение заголовка X-Mode, приводит его к нижнему регистру и сравнивает со строкой debug.
Actions
Actions описывают, что плагин должен сделать после успешной проверки условия.
| Action | Что делает |
|---|---|
|
Переписывает URI запроса через |
|
Добавляет или изменяет request header перед отправкой запроса в upstream. |
|
Удаляет request header. |
|
Выбирает заранее созданный upstream по его |
|
Возвращает HTTP-ответ из ADC без обращения к upstream. |
|
Возвращает отказ. По умолчанию использует статус |
|
Останавливает дальнейшее выполнение 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, добавляет служебный заголовок и отправляет запрос в upstreamorders_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 возвращает ошибку для остальных случаев.