Глава 25 / 51
Том 3 · Разработка протоколов (финал)

Перехват соединений: XS_TAKEOVER — третий путь

Часть бизнеса не влезает в «вопрос-ответ»: SOCKS-прокси, приватные протоколы после Upgrade, шлюзы длинного опроса. XS_TAKEOVER позволяет RequestProc забрать соединение целиком и вести его самому.

Обзор

Позиционирование: финал тома 3. Перехват — самое тяжёлое из трёх состояний: он переворачивает разделение по умолчанию «драйвер ведёт соединение, вы делаете контент» в «соединение ведёте вы». В правильном сценарии (смена протокола/туннели) это единственное решение; в неправильном — ненужная сложность.

25.1 Введение

Границы трёх состояний нарисованы в главе 4: XS_OK возвращает соединение в keep-alive, XS_FALLBACK отдаёт статическому слою. Третье состояние XS_TAKEOVER значит «с этого момента соединение моё» — драйвер выходит из обработки событий, и дальнейший приём-передача, тайм-ауты и закрытие решает логика приложения. Типовые сцены: HTTP Upgrade к приватному протоколу, туннели-прокси, push-шлюзы с длинными межзапросными сеансами.

25.2 Семантика перехвата

RequestProc ──▶ XS_TAKEOVER │ ┌────────────┴────────────┐ │ драйвер: выходит из цикла keep-alive │ /* дальнейшие запросы не разбираются */ │ приложение: меняет таблицу событий + свой ввод-вывод │ /* pull / Future / потоки */ │ терминал: приложение в конце концов Close │ └────────────┬────────────┘ ▼ Терминал Close Destroy-ит XServer и освобождает lease поколения
  • Точка перехвата: с момента возврата TAKEOVER из RequestProc соединение больше не потребляется HTTP-драйвером
  • Таблица событий: после перехвата ставится своя обработка (заменять на нелегальное состояние нельзя)
  • Контракт утилизации: объект соединения в конце всё равно Destroy-ит XServer — вы им управляете, но не владеете его деструкцией; терминал Close одновременно освобождает lease поколения, который держит соединение
  • Байтовая синхронизация: потреблённые/записанные до перехвата байты по-прежнему сверяются драйвером — ползапроса HTTP + TAKEOVER отклоняется как несогласованное состояние

25.3 Модель pull на практике

В func_test есть реальный кейс перехвата-эха; паттерн (асинхронный pull-ввод-вывод):

Перехват + асинхронное эхо (паттерн кейса)паттерн по кейсу takeover из tools/func_test.py
typedef struct TakeoverCtx {
    XS_HostInfo*  pHost;      /* через потоки — по правилам владения */
    xnetstream*   tcp;        /* или tls: транспорт после перехвата */
} TakeoverCtx;

static XS_RequestResult RequestProc(XS_HttpReq* pReq)
{
    if ( !IsUpgradeTarget(pReq) ) return XS_FALLBACK;

    /* 1. сначала записать требуемые протоколом байты ответа (например 101 Switching Protocols) */
    /* 2. построить контекст перехвата */
    TakeoverCtx* pCtx = xrtCalloc(1, sizeof(TakeoverCtx));
    pCtx->tcp = pReq->tcp;

    /* 3. повесить асинхронное чтение: данные приходят в колбэк (кейс — отдельный поток + RecvAsync) */
    StartTakeoverLoop(pCtx);

    return XS_TAKEOVER;   /* соединение моё */
}

Что только что произошло: внутри цикла перехвата вы работаете с pCtx->tcp обычными потоковыми API xrt — ровно как в гл. 23. Разница лишь в «происхождении» потока: это было HTTP-соединение, а какой протокол оно говорит дальше — решаете вы.

25.4 Терминальное состояние и утилизация

  • Вы обязаны в итоге Close: перехват — обещание; забытое соединение висит вечно (idle_timeout — подстраховка, а не проектный путь)
  • Close идёт через семантику закрытия потоковых API; XServer в терминальном колбэке соединения делает Destroy и освобождает lease поколения — поэтому старое поколение может дождаться окончания всех перехваченных соединений до утилизации (reload_matrix проверил этот порядок)
  • Контекст перехвата (pCtx) освобождается вместе с Close — тот же реестровый паттерн чистки, что у WS/TCP
Сначала спросите «перехват правда нужен?»: обычный бизнес (даже SSE/длинный опрос) делается на chunked + штатных колбэках (упражнение гл. 15). Перехват — для смены протокола и туннелей: это аварийный выход, не повседневный инструмент.

25.5 Контракт

  • Перехват: до возврата TAKEOVER дописаны байты ответа протокола; после перехвата драйвер соединение не потребляет.
  • Терминал: финальный Close за приложением; Destroy и освобождение lease — за XServer на терминале.
  • Контекст: жизнь контекста перехвата привязана к терминалу соединения — чистка реестровым паттерном.

Частые ошибки

После перехвата забыли Close
Симптом
Таблица соединений медленно растёт; старые поколения не утилизируются (lease не возвращён).
Причина
Обещание перехвата не выполнено — у каждого TAKEOVER обязан быть путь Close (включая ветки ошибок).
Перехват на ползапроса
Симптом
Соединение немедленно разрывается.
Причина
Драйвер сверяет «заявленные скриптом байты vs сеть» — TAKEOVER при недопотреблённом запросе есть несогласованное состояние. Сначала дочитайте, потом перехватывайте.

Упражнения

БазовыйВоспроизведение перехвата-эха

Реализуйте /raw по паттерну 25.3: после перехвата — режим построчного эха (на уровне HTTP будто «протокол обновился»).

Приёмка: curl держит соединение и получает эхо любых строк; после разрыва утечек нет (таблица соединений пуста).
ПродвинутыйМини-прокси SOCKS5

Реализуйте метод CONNECT: после перехвата открыть к цели второй поток и качать в обе стороны (двухпоточный форвардинг xrt).

Приёмка: curl -x через ваш прокси ходит на любой HTTP-сайт; разрыв любой стороны чисто закрывает обе.
ВызовГорячая перезагрузка при перехвате

Держите перехваченное соединение активным, подайте reload скрипта и убедитесь: старое поколение ждёт конца перехваченного соединения до утилизации.

Приёмка: перехваченное соединение продолжает работать; в логе finalize старого поколения после закрытия соединения.

Итоги

ТезисВывод
Третье состояниеXS_TAKEOVER: драйвер отходит, приложение ведёт соединение целиком
Сценариисмена протокола / туннели / прокси — не повседневный инструмент
Дисциплина перехватасначала дописать байты ответа протокола; сверка потребления действует
Дисциплина терминалаобязателен финальный Close; Destroy и lease — за XServer
Итог тома 3четыре класса протоколов + маршрутизация/JSON/шаблоны/vhost в руках — в том 4, к горячей перезагрузке
Что дальше по xrt