14 символов · сигнатуры сверены с xsbase.h построчно

Справочник контрактных символов

Хост ищет колбэки в продукте компиляции TCC по имени символа; все они опциональны и объявляются по мере надобности. Имя символа и есть протокол — без функций регистрации, базовых классов и макросов.

Глобальный контракт

Представления заимствуются

Указатели / представления в параметрах колбэка (XS_HttpReq, структуры заголовков, представления сообщений) действительны только в стеке колбэка; значения выносить можно, указатели — сперва скопировать.

Unit не более одного раза

ServiceUnit выполняется не более одного раза за время жизни поколения; для кандидата, чья инициализация не завершилась, Unit ошибочно не вызовется.

Всё опционально

Все 14 символов экспортируются по мере надобности. host без RequestProc — чисто статический сайт; ws-хост без WsText/WsBinary отвечает 503.

Распределение по потокам: ServiceInit / ServiceSwap — в потоке сборки или управления reload; RequestProc / Ws* / Event* — в рабочих потоках движка (возможна конкурентность); ServiceUnit — в потоке GC поколения. Межпоточное общее состояние синхронизируйте сами.

Колбэки жизненного цикла (3)

ServiceInit жизненный цикл

Вызывается один раз после загрузки скриптов поколения — здесь строят таблицы маршрутов, открывают базы, инициализируют статическое состояние.

void ServiceInit(XS_HostInfo* pHost);
Момент: до публикации нового поколения Поток: сборка / управление reload Экспорт: опционален

Новое поколение видно снаружи только после полного завершения Init; внутри Init данные передачи берутся через xsSwapTake (см. API XServer).

Пример · проверено на реальной машинеисточник: demo-single/main.c
static void ServiceInit(XS_HostInfo* pHost)
{
    DB_Init();          /* открыть SQLite, создать таблицы */
    Page_Init();
    Template_Init();
    RouteHTTP_Init();   /* регистрация и компиляция маршрутов */
}

ServiceUnit жизненный цикл

Деструктор при уходе поколения на покой — освобождение в порядке, обратном Init. За всю жизнь выполняется не более одного раза.

void ServiceUnit(XS_HostInfo* pHost);
Момент: после обнуления ссылок старого поколения (поток GC) Гарантия: не более одного раза Экспорт: опционален

Точки запуска две: путь грациозной остановки процесса или возврат последней аренды старого поколения после горячей перезагрузки. У поколения с незавершённым Init вызова Unit не будет.

Пример · проверено на реальной машинеисточник: demo-single/main.c (освобождение в обратном порядке)
static void ServiceUnit(XS_HostInfo* pHost)
{
    RouteHTTP_Unit();   /* освободить шаблоны и таблицу маршрутов */
    Template_Unit();
    Page_Unit();
    DB_Unit();          /* закрыть базу */
}

ServiceSwap жизненный цикл

Перед сменой поколения экспортирует неразрушающий снимок межпоколенческого состояния; ServiceInit нового поколения забирает его через xsSwapTake.

bool ServiceSwap(XS_HostInfo* pHost, xvalue** ppShared);
Момент: после прохода необратимой точки фиксации Возврат: true = снимок валиден, можно фиксировать Экспорт: опционален
ПараметрНаправлениеПояснение
pHostвходуходящий host старого поколения
ppSharedвыходпринимает снимок xvalue*; владение через хост переходит новому поколению

Смысл неразрушающее: после экспорта снимка старое поколение может продолжать обслуживать (в крайних случаях фиксацию ещё и отменят) — не закрывайте ресурсы внутри Swap.

Пример · контрактный паттерн
static bool ServiceSwap(XS_HostInfo* pHost, xvalue** ppShared)
{
    xvalue* pState = xrtValueObject();
    xrtValueObjectSetNew(pState, XRT_STR_LITERAL("counter"),
        xrtValueInt64(G_iCounter));
    *ppShared = pState;
    return true;
}

Колбэк HTTP (1)

RequestProc HTTP

Вход каждого HTTP-запроса. Трёхзначный возврат объявляет, «кто отвечает за остаток жизни этого запроса».

XS_RequestResult RequestProc(XS_HttpReq* pReq);
Момент: после разбора заголовков запроса Поток: worker движка (возможна конкурентность) Экспорт: опционален (нет = чисто статический host)
ВозвратСемантикаЧто драйвер делает дальше
XS_OK (0)полный ответ записанвычитать остаток body, продолжить keep-alive
XS_FALLBACK (1)не обработаноGET/HEAD — в статический слой, остальное — 404
XS_TAKEOVER (2)захват всего соединениядрайвер отходит; приложение само ведёт приём-передачу и в конце Close

XS_HttpReq — структурное представление: ровно одно из tcp/tls не NULL (доступен весь потоковый API xrt), head — разобранные заголовки запроса (с MethodCode), body — автомат фреймизации, host/server — принадлежность. Поля построчно — в справочнике структур.

Пример · проверено на реальной машинеисточник: demo-single/route_http/basic.h
/* маршрутизационные фреймворки demo все сходятся к RequestProc */
static XS_RequestResult RequestProc(XS_HttpReq* pReq)
{
    if ( RouteHTTP_Dispatch(pReq) ) return XS_OK;
    return XS_FALLBACK;   /* без совпадения -> wwwroot */
}

Колбэки WebSocket (6)

XServer выполняет проверку рукопожатия и xrtWsStreamAttach; маскировка, пересборка фрагментов, управляющие кадры и permessage-deflate целиком обрабатываются в слое xrt. Отправка — семейством xrtWsStreamText / Binary.

WsOpen WebSocket

Срабатывает при установлении соединения (101 пройден, поток подключён).

void WsOpen(XS_HostInfo* pHost, xwsstream* pWs);
Поток: workerЭкспорт: опционален
Пример
static void WsOpen(XS_HostInfo* pHost, xwsstream* pWs)
{
    (void)xrtWsStreamText(pWs, xrtStrView("connected"));
}

WsText WebSocket

Срабатывает после пересборки целого текстового сообщения; tText — представление, действительное внутри этого колбэка.

void WsText(XS_HostInfo* pHost, xwsstream* pWs, xstrview tText);
Поток: workerНе экспортирован: host отвечает 503
Пример · echo
static void WsText(XS_HostInfo* pHost, xwsstream* pWs, xstrview tText)
{
    (void)xrtWsStreamText(pWs, tText);
}

WsBinary WebSocket

Срабатывает на целое бинарное сообщение; накопленное превышение ws_message_limit завершает соединение close 1009.

void WsBinary(XS_HostInfo* pHost, xwsstream* pWs, xbytesview tData);
Поток: workerНе экспортирован: host отвечает 503

WsPing WebSocket

Пришёл кадр Ping. Автоматический ответ Pong обрабатывает слой xrt — этот колбэк для бизнес-наблюдения (статистика сердцебиений и т.п.).

void WsPing(XS_HostInfo* pHost, xwsstream* pWs, xbytesview tData);
Поток: workerЭкспорт: опционален

WsPong WebSocket

Пришёл кадр Pong — место, где измеряется время оборота.

void WsPong(XS_HostInfo* pHost, xwsstream* pWs, xbytesview tData);
Поток: workerЭкспорт: опционален

WsClose WebSocket

Закрытие соединения; несёт код состояния и представление причины — до 123 байтов UTF-8.

void WsClose(XS_HostInfo* pHost, xwsstream* pWs, uint16 iCode, xstrview tReason);
Поток: workerЭкспорт: опционален
Частые iCodeСмысл
1000нормальное закрытие
1009сообщение сверх лимита (ws_message_limit)
1011внутренняя ошибка сервера (например, OOM)

Колбэки потока TCP (3)

XS_StreamConn — структурное представление: ровно одно из tcp / tls не NULL; используйте отправочный API, соответствующий ненулевой стороне (под tcps запрещено писать в tcp в обход tls). Время жизни соединения принадлежит хосту — приложение не отвечает за Destroy.

EventOpen TCP

Срабатывает после установления потокового соединения (момент отправить приветственный баннер).

void EventOpen(XS_HostInfo* pHost, XS_StreamConn* pConn);
Поток: workerЭкспорт: опционален
Пример · баннер
static void EventOpen(XS_HostInfo* pHost, XS_StreamConn* pConn)
{
    if ( pConn->tcp != NULL )
        (void)xrtNetStreamSend(pConn->tcp, "hello\n", 6);
}

EventData TCP

Пришли данные. pBuffer — цепочка буферов без копирования (заимствована в колбэке); размер — через xrtNetBufSize, данные потребляются буферными API xrt.

void EventData(XS_HostInfo* pHost, XS_StreamConn* pConn, xnetbuf* pBuffer);
Поток: workerЗаимствование: pBuffer действует только внутри колбэка

EventClose TCP

Конечное состояние закрытия соединения; включает код сетевого результата и опциональный объект ошибки (заимствованный).

void EventClose(XS_HostInfo* pHost, XS_StreamConn* pConn, xnetresult iResult, const xerror* pError);
Поток: workerРесурсы: соединение разрушает хост

Колбэк UDP (1)

EventDgram UDP

Срабатывает по одному разу на дейтаграмму; состояния соединения нет — каждый пакет берёт аренду текущего поколения через слот прослушивания.

void EventDgram(XS_HostInfo* pHost, xnetudp* pUdp, const xnetudpmessage* pMsg);
Поток: workerЗаимствование: pMsg действует только внутри колбэка
Поле pMsgПояснение
Remoteадрес источника (перевод в текст — семейством xrtNetAddr)
Data / Sizeнагрузка и длина
FlagsXNET_UDP_MESSAGE_TRUNCATED и другие
Пример · отослать источнику
static void EventDgram(XS_HostInfo* pHost, xnetudp* pUdp,
    const xnetudpmessage* pMsg)
{
    (void)xrtNetUdpSendTo(pUdp, &pMsg->Remote, "ack", 3);
}