Запуск и опрос: отдаём перезагрузку тем, кому нужно
XServer не предписывает «кто и когда перезагружает» — он даёт лишь Submit/Query. Глава проектирует поверхность запуска: управляющие маршруты для эксплуатации, наблюдение файлов для разработки и представление результатов.
Обзор
27.1 Введение
Дизайн запуска отвечает на три вопроса: кто может запускать (полномочия), с какой гранулярностью (host / server / all) и как узнать результат. Занятно, что XServer оставляет все три приложению — потому что «плоскость управления принадлежит слою приложения» (яма из гл. 1). Глава даёт два проверенных ответа.
27.2 Паттерн управляющего маршрута
static void Handle_Admin_Reload(XS_HttpReq* pReq, const RouteParamHTTP* a, uint32 n)
{
xvalue* pOut = xrtValueObject();
XS_ReloadId iId;
/* 1. аутентификация: плоскость управления обязана сидеть за границей доверия
* (здесь фиксированный токен; в продакшн подключается любая система) */
if ( !CheckAdminToken(pReq) ) {
(void)ReplyText(pReq, 403, "forbidden");
goto Out;
}
/* 2. гранулярность: scope=host|server|all */
if ( ScopeIsAll(pReq) ) iId = xsReloadAllSubmit();
else if ( ScopeIsServer(pReq) ) iId = xsReloadServerSubmit(sName);
else iId = xsReloadHostSubmit(pReq->host);
/* 3. немедленно ответить приёмом (без ожидания терминала) */
if ( iId == 0 ) {
(void)ReplyText(pReq, 503, "reload queue full");
goto Out;
}
/* out: {"id":123} — результат через /admin/reload/status?id=123 */
Out:
xrtValueRelease(pOut);
}
Что только что произошло — проектные решения:
- Приём отделён от результата: POST сразу отдаёт ID, результат — отдельный GET-маршрут; ни один поток запроса не ждёт терминала (Submit и так неблокирующий — ждать тем более нельзя)
- Три гранулярности: на каждый день host (минимальный радиус поражения); структурные изменения — server; глобальная правка конфигурации — all (семантика пакетной транзакции)
- Аутентификация — первой строкой приложения: управляющий маршрут — это «маршрут, меняющий поведение сервиса»; полномочия те же, риск другой
27.3 Автозапуск: сохранил — перезагрузилось
Триггер dev-опыта — наблюдение каталога devfile и подача при изменении:
/* ServiceInit: запустить наблюдение + периодические проверки (наблюдение xrt / опрос mtime — оба варианта) */
static void OnSrcChanged(void* pUserData)
{
XS_HostInfo* pHost = (XS_HostInfo*)pUserData;
/* антидребезг: шторм сохранений подаёт только последний (latest-wins как подстраховка) */
(void)xsReloadHostSubmit(pHost);
}
/* внимание: таймеры умирают со своим поколением — в Init нового перевесить наблюдение */
Антидребезг двухслойный: приложение склеивает быстрые подряд идущие изменения в коротком окне; а если промахнулось — latest-wins выдавит просроченные намерения в SUPERSEDED — худший случай такого триггера: пара лишних компиляций, промежуточное состояние никогда не выйдет в свет. В продакшне автозапуск обычно выключен: перезагрузка — управляемое изменение, а не событие файловой системы.
27.4 Представление результатов
static void Handle_Admin_ReloadStatus(XS_HttpReq* pReq, const RouteParamHTTP* a, uint32 n)
{
XS_ReloadResult t;
xvalue* pOut = xrtValueObject();
if ( !xsReloadQuery(ParseId(pReq), &t) ) {
(void)ReplyText(pReq, 404, "no such id");
return;
}
xrtValueObjectSetNew(pOut, XRT_STR_LITERAL("id"),
xrtValueInt64((int64)t.Id));
xrtValueObjectSetNew(pOut, XRT_STR_LITERAL("state"),
xrtValueInt64((int64)t.State));
xrtValueObjectSetNew(pOut, XRT_STR_LITERAL("revision"),
xrtValueInt64((int64)t.Revision));
xrtValueObjectSetNew(pOut, XRT_STR_LITERAL("message"),
xrtValueString(xrtStrView(t.Message))); /* причина FAILED */
(void)ReplyJSON(pReq, 200, pOut);
xrtValueRelease(pOut);
}
Терминал приходит за десятки миллисекунд — секунды (по объёму скриптов); фронтенд может опрашивать или обновлять — Query идемпотентен и без побочных эффектов.
27.5 Контракт
- Аутентификация: у управляющего маршрута обязательна прикладная проверка; он меняет поведение сервиса — это не обычный бизнес-эндпоинт.
- Гранулярность: по умолчанию host; all — только для глобальных изменений конфигурации (пакетная транзакция).
- Представление: приём отделён от результата; опрос идемпотентен.
Частые ошибки
- Симптом
- Доступный из интернета /admin/reload — любой может сменить код вашего сервиса.
- Причина
- Поверхность запуска — это поверхность изменений. Минимум: отдельный vhost (привязка к интрасети) + токен; лучше — дизайн границы доверия из гл. 44.
- Симптом
- После первой горячей перезагрузки автотриггер умирает.
- Причина
- Таймеры/дескрипторы наблюдения умирают со старым поколением (гл. 30) — ServiceInit нового поколения обязан перевесить наблюдение. Обратная сторона «бесплатной безопасности» изоляции поколений.
Упражнения
Реализуйте подачу scope=host/server/all и опрос status; прогоните весь поток curl'ом.
Добавьте к управляющему маршруту сверку токена за постоянное время; неверные токены — с ограничением скорости (идея деградации из гл. 22).
Реализуйте «сохранил — перезагрузилось» на опросе mtime (антидребезг + межпоколенческий перевес); измерьте сквозную задержку от сохранения до вступления в силу.
Итоги
| Тезис | Вывод |
|---|---|
| Три вопроса запуска | кто (аутентификация), гранулярность (host/server/all), результат (Query) |
| Управляющий маршрут | приём отделён от результата; аутентификация первой строкой приложения |
| Автозапуск | наблюдение + Submit; антидребезг двухслойный (склейка + latest-wins) |
| Поколения | триггер умирает с поколением; перевес в новом Init |
- справочник xrt · file — материал для опроса mtime и наблюдения файлов
- справочник xrt · hash — примитивы для сравнения токенов за постоянное время