Глава 17 / 51
Том 3 · Разработка протоколов

Маршрутизация: статическая таблица + динамические pattern + слоты методов

Хост не поставляет роутер — это дизайн, а не недостача. Глава отдаёт вам каркас маршрутизации, проверенный в demo-single и обкатанный routebench на десяти тысячах маршрутов: две таблицы, одна компиляция, три возврата.

Обзор

Позиционирование: маршрутизация — узел HTTP-бизнеса. Готовый продукт главы — modules/protocol.h демо, забирайте как есть. Реальное поведение 405+Allow замерено в главе 2; здесь объясняется его происхождение.

17.1 Введение

«Забирать ли роутер в хост?» — вопрос настоящий: забрать — и гибкие потребности путей (регулярки, префиксы, wildcards) начинают давить на конфигурационную систему; оставить — каждое приложение пишет своё. Выбор XServer — давать детали, а не изделие: xrtMap (хэш-таблица) для точных совпадений, xrtPattern (пакетно прекампилируемый сопоставитель) для параметризованных путей — двадцать строк сборки дают продакшн-роутер, а особые потребности (регулярки, версионные префиксы) прорастают в любой момент.

17.2 Паттерн двух таблиц

route.h — реестр регистрации (реальный исходник)проверено на реальной машинеисточник: demo-single/route.h
static void RouteHTTP_Init(void)
{
    G_StaticRouteTableHTTP = xrtMapCreate(sizeof(RouteInfoHTTP));

    /* статическая таблица: точные URI (хэш xrtMap) */
    AddStaticRouteHTTP("/api/v1/items", XHTTP_METHOD_GET,  Handle_API_List);
    AddStaticRouteHTTP("/api/v1/items", XHTTP_METHOD_POST, Handle_API_Add);

    /* динамическая таблица: захват сегмента {id} (компилируется после регистрации) */
    AddDynamicRouteHTTP("/api/v1/items/{id}",
        XHTTP_METHOD_GET,                    Handle_API_Item);
    AddDynamicRouteHTTP("/api/v1/items/{id}",
        XHTTP_METHOD_PUT | XHTTP_METHOD_PATCH, Handle_API_Item_Update);
    AddDynamicRouteHTTP("/api/v1/items/{id}",
        XHTTP_METHOD_DELETE,                 Handle_API_Item_Delete);

    /* захват внутри сегмента тоже работает: /api/v1/file/{name}.txt */
    AddDynamicRouteHTTP("/api/v1/file/{name}.txt",
        XHTTP_METHOD_GET, Handle_API_File);

    /* динамические маршруты обязаны компилироваться один раз после всей регистрации */
    (void)RouteHTTP_Compile();
}

Что только что произошло: статическая таблица попадает за O(1); динамическая внутри RouteHTTP_Compile через xrtPatternCompileMany пакетно компилируется в неизменяемый сопоставитель — дальше стоимость сопоставления зависит только от длины пути и никогда от числа маршрутов. Сигнатура обработчика — конвенция демо: (XS_HttpReq*, const RouteParamHTTP* arrParam, uint32 iParamCount), arrParam несёт представления захваченного {id}.

17.3 Слоты методов и 405

  • Каждая пара (URI, метод) — слот; один URI с несколькими методами регистрируется раздельно; точное повторение URI и метода перекрывает старый колбэк с предупреждением
  • Маски методов комбинируются свободно: PUT | PATCH делят слот; XHTTP_METHOD_CRUD (пять методов) и XHTTP_METHOD_ANY (включая расширения) — агрегатные макросы (справочник перечислений)
  • URI попал, метод нет → 405 + заголовок Allow: Allow собирается из всех методов, зарегистрированных на этом URI, — источник HEAD /json → 405, Allow: GET из главы 2
Хотите, чтобы слот GET принимал и HEAD: регистрируйте GET | HEAD — ваш ReplyRaw уже единообразно не шлёт тело для HEAD (глава 15), поэтому большинство обработчиков поддерживают HEAD без единой правки.

17.4 Порядок сопоставления и производительность

запрос → ① статическая таблица (точное попадание xrtMap → слот метода) промах → ② динамические pattern (совпадение → слот метода → захваченные параметры) промах → XS_FALLBACK → статический слой (wwwroot) / 404

Цифры производительности (routebench, 3 раунда × 10 000 запросов, среднее):

Число маршрутовЗадержка XServerСравнение
1042,0 мксGo 42,2
10 00059,1 мкс (+41%)PHP +492% / FastAPI +935%

Секрет плоскости: статическая таблица — хэш O(1); динамическая после пакетной компиляции сопоставляет за O(длины пути) — десять тысяч маршрутов дороже десяти лишь на 41%, тогда как сравниваемые стеки на десяти тысячах деградируют на порядок.

17.5 Контракт

  • Компиляция: после регистрации динамических маршрутов обязателен один Compile; дальше таблица неизменяема (пересборка при смене поколения — работа горячей перезагрузки).
  • Порядок: статические → динамические → FALLBACK; при одном URI статическое приоритетнее динамического.
  • Параметры: arrParam — представление уровня запроса, в колбэке используется и выбрасывается.

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

Забыт RouteHTTP_Compile
Симптом
Все динамические маршруты 404/падают в статику; статические работают.
Причина
Динамическая таблица становится сопоставимым неизменяемым pattern только после Compile — регистрация лишь собирает. Демо ставит Compile в конец RouteHTTP_Init с комментарием «обязательно».
Динамические маршруты ширятся и ширятся
Симптом
Маршруты-всё-видишь вроде "/api/{a}/{b}/{c}" перехватывают трафик друг друга и попадают не в свои обработчики.
Причина
pattern сопоставляется точными сегментами (не префиксы и не регулярки); но сваливание многих ресурсов в один паттерн размывает семантику. Разные ресурсы — разные pattern.

Упражнения

БазовыйREST из семи слотов

Реализуйте полный REST для /api/v1/users и /api/v1/users/{id} (GET список / POST создание / GET элемент / PUT+PATCH обновление / DELETE удаление + HEAD списка).

Приёмка: curl -I по списку даёт 200 (HEAD работает); DELETE по списку даёт 405 с Allow из пяти методов.
ПродвинутыйСверка семантики 405

Через -v пронаблюдайте несколько запросов «URI попал, метод нет» и сверьте заголовок Allow со своими регистрациями.

Приёмка: множество Allow равно объединению всех зарегистрированных методов URI — без лишних и без пропущенных.
ВызовГенератор тысячи маршрутов

Скриптом сгенерируйте 1000 статических + 1000 динамических маршрутов (см. генератор tools/routebench); замерьте время компиляции при старте и задержку запросов.

Приёмка: компиляция < 100 мс; рост задержки против 20 маршрутов < 50%.

Итоги

ТезисВывод
Две таблицыстатическая xrtMap O(1) + динамическая xrtPattern, плоско после компиляции
Слоты методовслот на (URI × метод); маски комбинируются; агрегаты CRUD/ANY
405URI попал, метод нет → 405 + Allow (объединение зарегистрированных методов)
Порядокстатика → динамика → FALLBACK → статический слой / 404
Замеры10k маршрутов 59,1 мкс (+41%); сравниваемые стеки деградируют на порядок
API, затронутые в главе
Что дальше по xrt