Работа со Сценариями (lua-скриптами)

Основные возможности

С помощью Сценариев (lua-скриптов) можно:

  • выбирать Сервер Балансировки (Content Switching (cs));

  • изменять содержимое запросов/ответов (Rewrite);

  • отвечать на HTTP-запрос (Responder);

  • прерывать нежелательные соединения.

lua-скрипты применяются к:

  • Виртуальному Серверу для:

    • выбора Сервера Балансировки (Content Switching (cs));

    • ответа на HTTP-запрос (Responder);

    • изменения содержимого запросов (Rewrite);

    • прерывания нежелательного соединения;

  • Серверу балансировки для:

    • ответа на HTTP-запрос (Responder) (например, при условии получении ответа);

    • изменения содержимого ответов (Rewrite);

    • прерывания нежелательного соединения (например, при условии получении ответа).

Основные положения

lua-скрипты привязываются к объекту с порядковым номером, определяющим очередность выполнения скрипта.

Пример:

set vs HTTP Vs01 luarules 10 script first.lua
set vs HTTP Vs01 luarules 15 script second.lua

Важно учитывать:

  • правила в lua-скриптах обрабатываются по порядку. В примере выше сначала обрабатываются правила из скрипта first.lua, потом (если не было совпадений) правила из скрипта second.lua;

  • правила могут располагаться как в одном файле, так и в нескольких;

  • обработка правил останавливается после выражения: client.action = “xxx”.

Выражение 'client.action'

Выражение 'client.action' определяет следующие варианты действий:

  • client.action = "bs" – возвращает Сервер Балансировки, определенный в client.bs = “<Имя_Сервера_Балансировки>”;

  • client.action = "respond" – возвращает ответ пользователю, не передавая запрос на Реальный Сервер.Содержимое ответа описывается в client.respond.status;

  • client.action = "drop" – сбрасывает соединение;

  • client.action = ”pass” – используется в скриптах для модификации ответов, после этого ответ передается клиенту;

  • client.action = ”auth” – останавливает обработку для аутентификации.

Условия выполнения

Условия выполнения того или иного действия определяются:

  • логическими выражениями if, elseif, else, end;

  • любыми данными, полученными из запроса, а также их комбинацией. Например:

    • if client.http_req.host == "abc.ru” then – eсли имя хоста из запроса эквивалентно «abc.ru», то выполнить какое-либо действие;

    • if (client.http_req.host == "abc.ru” and client.remote_p.ip == "10.140.0.200") then – eсли имя хоста из запроса эквивалентно «abc.ru», и запрос пришел с IP-адреса 10.140.0.200, то выполнить какое-либо действие.

Можно использовать возможности языка для работы с полученными данными, поскольку правила с четким соответствием не всегда применимы.

Пример для curl qwe.123.ru/abczxc (см. Пример выражений для работы с данными):

> GET /abczxc HTTP/1.1
> Host: qwe.123.ru
> User-Agent: curl/7.81.0
Таблица 1. Пример выражений для работы с данными
Выражение Результат Комментарий

if client.http_req.path == "/abcZxc"

true

Эквивалентно

if client.http_req.path == "/abc"

false

if (client.http_req.path:find("zx"))

true

Содержит значение

(client.http_req.host:match("(.*).123.ru")

qwe

Вернет строку, значение ДО « .123.ru»

Примеры для Content Switching

Пример 1. Передача любого запроса на Сервер Балансировки «lb1»:

client.bs = "lb1"
client.action = "bs"

Пример 2. Если имя хоста в запросе точно соответствует «abc.domain.ru», то направить запрос на Сервер Балансировки «lb1». В противном случае вернуть код ответа 403 (по умолчанию вернется ошибка 503):

if client.http_req.host == "abc.domain.ru" then
        client.bs = "lb1"
        client.action = "bs"
else
        client.respond.status = 403
        client.action = "respond"
end

Пример 3. Если имя хоста содержит текст «abc» и запрос пришел из сети 192.0.2.0/24, то вернуть Сервер Балансировки «lb1». В противном случае вернуть код ответа 403 (по умолчанию вернется ошибка 503):

if (client.http_req.host:find("abc") and client.remote_p:is_network("192.0.2.0/24"))
then
        client.bs = "lb1"
        client.action = "bs"
else
        client.respond.status = 403
        client.action = "respond"
end

Пример 4. Если путь содержит:

  • «red», то вернуть Сервер Балансировки «lb1»;

  • «green», то вернуть Сервер Балансировки «lb2».

Если не сработало ни одно из правил, то вернуть Сервер Балансировки «lb-default».

Код:

if client.http_req.path:find("red") then
        client.bs = "lb1"
        client.action = "bs"
elseif client.http_req.path:find("green") then
        client.bs = "lb2"
        client.action = "bs"
else
        client.bs = "lb-default"
        client.action = "bs"
end

Примеры для Rewrite

Пример 1. Если имя хоста содержит текст «abc» И запрос пришел из сети 192.0.2.0/24, то добавить заголовок «XFF: <IP-адрес_пользователя_при_подключении>», и перенаправить запрос на Сервер Балансировки «lb1».

Код:

if (client.http_req.host:find("abc") and client.remote_p:is_network("192.0.2.0/24")) then
        client.http_req.header:field_set("XFF", client.remote_p.ip)
        client.bs = "lb1"
        client.action = "bs"
end

Результат:

WEB -- 03<p>Method GET</p><p>URL on server: /</p><p>REQ
Headers: </p>Host: abc.domain.ru
User-Agent: curl/7.81.0
Accept: */*
XFF: 192.0.2.5

Пример 2. Если имя хоста начинается с «abc», то добавить заголовок «XFF: <IP-адрес_пользователя_при_подключении>» и «Remote-port: <порт-источник_на_клиенте>», и перенаправить запрос на Сервер Балансировки «lb1».

Код:

function startswith(text, prefix)
  return text:find(prefix, 1, true) == 1
end

if startswith(client.http_req.host, "abc") then
  client.http_req.header:field_set("Remote-port", client.remote_p.port)
  client.http_req.header:field_set("XFF", client.remote_p.ip)
  client.bs = "lb1"
  client.action = "bs"
end

Пример 3. Если:

  • путь запроса содержит «app», то при наличии заголовка X-Forwarded-For добавить IP-адрес клиента в конец запроса;

  • заголовка не было в запросе, то добавить новый.

Перенаправить запрос на Сервер Балансировки «lb1».

Код:

if client.http_req.path:find("abc") then
        if client.http_req.header:field_count("X-Forwarded-For") > 0 then
                xff, xffip = client.http_req.header:field_get("X-Forwarded-For")
                client.http_req.header:field_set("X-Forwarded-For", xffip ..", "..client.remote_p.ip)
        else
                client.http_req.header:field_set("X-Forwarded-For", client.remote_p.ip)
        end
client.bs = "lb1"
client.action = "bs"

end

Пример 4. Подменить домен «ru» на домен «local» (например, если запрос идет к app.domain.ru, то в сторону Реального Сервера должен прийти app.domain.local). В начале пути запроса добавить «/external» и перенаправить запрос на Сервер Балансировки «lb1».

Код:

client.http_req.host =  client.http_req.host:match("(.*).ru") .. ".local"
client.http_req.path = "/external" .. client.http_req.path
client.bs = "lb1"
client.action = "bs"

Примеры для Responder

Пример 1. Если метод не GET или не HEAD, то сбросить соединение.

Код:

if (client.http_req.method ~= "GET" and client.http_req.method ~= "HEAD") then
    client.action = "drop"
end

Пример 2. Перенаправить соединение с HTTP на HTTPS.

Код:

client.respond.status = 302
client.respond.header["Location"] = "https://" .. client.http_req.host .. client.http_req.path
client.respond.header["Connection"] = "close"
client.action = "respond"

Пример 3. Если запрос из сети 192.0.2.0/24, то ответить HTML-страницей и параметрами запроса.

Код:

if client.remote_p:is_network("192.0.2.0/24") then
    client.respond.header["Connection"] = "close"
    client.respond.header["Content-type"] = 'text/html
    client.respond.body = [[<html>
    <body>
    <meta charset="UTF-8">
    <h1>Lets goodbye!</h1>
    <p>Доступ запрещен</p>
    </body>
    </html>]] .. "IP: " .. client.remote_p.ip .. "\n TRY: " .. client.http_req.host .. client.http_req.path .. "\n" ..      " Vserver: " .. client.local_p.ip .. ":" .. client.local_p.port
client.action = 'respond'

Примеры для Ratio (F5)

Пример процентного разделение запросов:

  • примерно 80% запросов попадет на Сервер Балансировки «lb01»;

  • примерно 20% запросов попадет на Сервер Балансировки «lb02».

За каждым Сервером Балансировки может находиться свой пул серверов или один Реальный Сервер.

Код:

if math.random(1, 100) <= 20 then
  client.action = "bs"
  client.bs = "lb01"
else
  client.action = "bs"
  client.bs = "lb02"
end

Примеры для страницы обслуживания («Sorry page»)

Пример 1. Если Сервер Балансировки «lb01» находится в состоянии «Online», то он будет выбран для подключения. В противном случае будет показана страница sorry.html.

Файл sorry.html должен располагаться в директории /var/lib/tdc/www.

Код:

if client:bs_state("lb01") == "ONLINE" then
    client.bs = ”lb01"
    client.action = "bs"
else client.respond.header["Content-Type"] = "text/html"
    client.action = 'respond’
    local filename = storage:sub_path('www', 'sorry.html’)
    local file, err = io.open(filename)
    client.respond.body = file:read("*a")
    file:close()
end

Пример 2. Страница обслуживания:

  • отправляется в интервале времени с 19:00 до 20:00, событие записывается в журнал;

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

Код:

if client:bs_state("lb01") == "ONLINE" then
    client.bs = ”lb01"
    client.action = "bs"
else client.respond.header["Content-Type"] = "text/html"
    client.action = 'respond’
    local filename = storage:sub_path('www', 'sorry.html’)
    local file, err = io.open(filename)
    client.respond.body = file:read("*a")
    file:close()
end

Примеры для Сервера Балансировки

Пример 1. Удаление заголовка с именем «ETag» из ответа Реального Сервера.

Код:

client.http_resp.header["ETag"] = nil
client.action = "pass"

Пример 2. Добавление заголовка со временем и именем «RESTIME».

Код:

client.http_resp.header["RESTIME"] = os.date()
client.action = "pass"

Пример 3. Добавление заголовка с Реальным Сервером (именем «RS») со значением IP-адреса и порта Реального Сервера, от которого получен ответ.

Код:

client.http_resp.header["RS"] = tostring(client.rs)
client.action = "pass"

Пример 4. Генерация ответа с условием. Если Реальный Сервер ответил с кодом 500, то Termidesk Connect сгенерирует ответ с кодом 201.

Код:

if client.http_resp.status == 500 then
        client.respond.header["Connection"] = "close"
        client.respond.header["Content-Type"] = "text/html"
        client.respond.status = 201
        client.action = ”respond”
end

Пример 5. Замена заголовка «Server» в ответе.

Код:

client.http_resp.header["Server"] = "MYServer"