action_script — PHP-скрипт на потоке¶
Когда декларативного конструктора (фильтры, веса, схемы) не хватает, поток со схемой action script отдаёт решение о маршрутизации вашему PHP-коду, который исполняется на каждом клике.
Как включить¶
PHP-рантайм включён по умолчанию при установке — если
вы не ставили с --no-php, всё уже работает.
- В редакторе потока выберите схему action script (PHP).
- Напишите скрипт.
- Кнопка ▶ Test прогоняет его на синтетическом клике прямо в редакторе:
покажет решение, изменённые sub'ы и cost, вывод
echoи ошибки с номером строки. Страну, устройство и sub'ы для тестового клика задают рядом с кнопкой. Отлаживаться на живом трафике не нужно.
Если PHP выключен, скрипт сохранится, но исполняться не будет: клик пойдёт
обычной маршрутизацией, а в трейсе на фазе action_script будет ошибка
script: engine disabled (PHP_FPM_ADDR not set). Кнопка ▶ Test в этом
случае отвечает «PHP-FPM не настроен». Включить PHP позже:
install.sh update --with-php.
Правила исполнения¶
- Скрипт пишется без
<?php(с тегом тоже можно — он отбрасывается). - Бюджет на клике — 100 мс. Обычный скрипт укладывается в 1–5 мс. Сетевых
вызовов (curl,
file_get_contentsнаружу) не делайте: не успеют. У Test-прогона бюджет больше — 2 секунды, чтобы холодная компиляция не читалась как таймаут. - Память — 128 МБ на исполнение: потолок задаёт пул php-fpm
(
php_admin_value[memory_limit] = 128Mвzz-minik.conf), иini_set()из скрипта его не меняет. - Одновременно исполняется не больше 8 скриптов. Девятый клик ждёт свободный слот не дольше 10 мс и, если тот не освободился, уходит обычной маршрутизацией.
- Fail-open: ошибка, таймаут, переполнение или недоступный PHP-FPM никогда
не роняют клик — он маршрутизируется как обычный поток. Текст ошибки виден в
Traffic log, фаза
action_script. - Синтаксис проверяется при сохранении потока и перед каждым Test-прогоном. Сохранение отклоняет только явную синтаксическую ошибку; если PHP-FPM недоступен, поток сохранится без проверки.
$click — контекст клика¶
Массив, только чтение: он приходит в скрипт копией, и записи прямо в $click
до трекера не доходят — для этого есть set_sub() и set_cost().
| Ключи | Что в них |
|---|---|
click_id, visitor_id |
идентификаторы клика и визитора |
subs |
все sub-параметры: $click['subs']['sub_id_5']. Там же лежат и остальные ключи query-строки под своими именами |
ip, user_agent, language |
сетевые атрибуты |
country, region, city, isp |
гео (страна — ISO-код) |
device, os, os_version, browser, browser_version, connection_type |
устройство |
referrer, keyword, search_engine |
источник перехода |
campaign_id, campaign_name, flow_id, flow_name, source_id |
сущности трекера |
cost, currency |
стоимость клика до override |
is_bot, is_unique, is_proxy, is_tor, is_ipv6, is_datacenter, is_vpn |
флаги (bool) |
now |
время клика в UTC, RFC3339 (у date() внутри скрипта — часовой пояс сервера) |
Функции¶
| Функция | Действие |
|---|---|
set_url(string $url, $type = '302') |
редирект. В URL работают {макросы} трекера — раскроются после скрипта |
send(int $status = 204) |
ответить голым статусом без редиректа (404/403/204). Значение вне 200–599 заменяется на 204 |
set_sub(string $name, $value) |
перезаписать sub до записи клика в статистику |
set_cost($cost) |
перезаписать стоимость клика (приводится к float) |
sub(string $name, string $default = '') |
шорткат чтения $click['subs'][$name] |
echo / print |
вывод попадает в лог Test-прогона и в трейс клика |
$type у set_url() — любой из типов редиректа потока: 301 302 303
307 meta double_meta js iframe form direct no_referrer curl
(remote — синоним curl). Что делает каждый — в таблице
типов редиректа. Неизвестное значение игнорируется:
останется тип, заданный в настройках потока.
set_sub() принимает любое имя, но в колонки клика пишутся только
sub_id_1…sub_id_30. Произвольное имя доживёт до макросов в URL назначения —
в отчёты оно не попадёт.
Если ни set_url, ни send не вызваны — скрипт «решения не принял»: клик
маршрутизируется лендингами и офферами потока как при обычной схеме, а
сделанные set_sub / set_cost всё равно применяются.
send() и set_url() не останавливают скрипт
Обе функции только записывают решение — код после них продолжает
выполняться, и следующий вызов перезапишет предыдущий. Чтобы выйти сразу,
ставьте return;. Именно return, а не exit/die: те обрывают ответ
PHP, трекер не сможет его разобрать и клик уйдёт обычной маршрутизацией.
Примеры¶
Подпись URL (HMAC), которую не собрать макросами:
$sig = hash_hmac('sha256', $click['click_id'] . '|' . sub('sub_id_1'), 'MY_SECRET');
set_url('https://network.example.com/offer?cid=' . $click['click_id'] . '&sig=' . $sig);
Детерминированный сплит — визитор всегда попадает на свой вариант:
$bucket = crc32($click['visitor_id']) % 100;
if ($bucket < 50) {
set_url('https://offer-a.example.com/?cid=' . $click['click_id']);
} else {
set_url('https://offer-b.example.com/?cid=' . $click['click_id'], 'js');
}
Роутинг по времени плюс чистка мусора:
if ($click['is_bot'] || $click['is_datacenter']) {
send(404); // боты и хостинги — мимо
return; // без return скрипт пойдёт дальше
}
$hour = (int)date('H'); // часовой пояс сервера
set_url($hour >= 22 || $hour < 6
? 'https://night-offer.example.com/?cid=' . $click['click_id']
: 'https://day-offer.example.com/?cid=' . $click['click_id']);
Нормализация sub'ов без смены маршрута:
set_sub('sub_id_10', strtolower(substr(sub('sub_id_2', 'na'), 0, 16)));
if (sub('sub_id_3') === '') {
set_sub('sub_id_3', 'organic');
}
Перенос скриптов с других трекеров¶
Язык тот же (PHP 8.3) — hash(), hash_hmac(), строковые и массивные функции
переносятся как есть. Отличается только API трекера.
| Классический трекер | Minik |
|---|---|
$rawClick->getCountry() и хелперы потока |
$click['country'], $click['device'], … |
getSubId(N) / токены {subN} |
sub('sub_id_N') |
| смена действия потока (URL) | set_url($url, $type) |
| «показать 404» / блок-действия | send(404) |
| правка токенов клика | set_sub($name, $value) |
| правка стоимости | set_cost($cost) |
file_get_contents / curl наружу |
не используйте: бюджет 100 мс, fail-open |
| KV-хранилище | не предусмотрено — держите состояние в sub'ах или снаружи |
Ограничения¶
- Скрипты исполняются отдельно от трекинг-движка, в контейнере php-fpm: зависший скрипт не останавливает приём трафика.
- Пропускная способность ограничена числом PHP-воркеров: один воркер ≈
1000 мс / среднее время скриптакликов в секунду. Инсталлятор ставитpm.max_children = 16вdeploy/php/zz-minik.conf— при заметном трафике на скриптовые потоки его можно поднять. Потолок в 8 параллельных исполнений на стороне трекера при этом не изменится: он зашит в коде и настройке не поддаётся. - Скрипт исполняется на браузерном клике (
/r/{alias}, он же/click/{alias}). Click API и MClient action-потоки не исполняют — там клик маршрутизируется лендингами и офферами потока.
Изоляция¶
Скрипт — это PHP на вашем сервере, а не песочница для чужого кода: схему
action script доверяйте только тем, кому доверяете сервер. Правка требует права
campaigns:edit и фиксируется в audit-логе.
Что ограничено штатной конфигурацией:
open_basedirна каждый запрос сужается до каталога этого потока — соседние потоки и лендинги других воркспейсов скрипту не видны;- запуск процессов отключён:
exec,passthru,shell_exec,system,proc_open,popen,pcntl_exec,pcntl_fork; - контейнер php-fpm живёт в отдельной docker-сети и не достаёт до Postgres, Redis, ClickHouse и Centrifugo;
request_terminate_timeout = 5sв пуле — страховка поверх бюджета клика.
Не редактируйте zz-minik.conf наугад
Эти ограничения — граница RCE для пользовательского PHP (скрипты и .php-лендинги). Ослабив их, вы отдаёте сервер автору скрипта.