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

Шаблоны и контролируемые страницы: два оружия динамического контента

Последний пазл серверного рендеринга: xrtTemplate отвечает за рендеринг, каталог page/ — за «видимость результата». Маршруты /template и /page демо — живые образцы главы.

Обзор

Позиционирование: вторая дорога помимо API — сервер отдаёт HTML. Глава заодно решает вопрос безопасности: какие файлы доступны по URL, а какие обязаны идти через скрипт. Читатели главы 10 снова встретят «защиту от обхода путей».

19.1 Введение

Не весь динамический контент заслуживает фронтенд-фреймворк: админ-панели, страницы отчётов и рендеринг писем естественно ложатся на серверные шаблоны. Ответ XServer держит разделение чистым — xrtTemplate (компилируемый шаблонизатор, 32 API) отвечает «как рендерить», а каталог page/ (вне wwwroot) — «можно ли получить напрямую»: шаблоны и контролируемые страницы не видны статическому слою и выходят только через ваши маршруты LoadPage/рендеринга.

19.2 Синтаксис шаблонов и рендеринг

Конвейер рендеринга (реальный поток Handle_Template демо)проверено на реальной машинеисточник: demo-single/route_http/basic.h
/* 1. подготовить данные (дерево xvalue, правила владения гл. 18) */
xvalue* pData = xrtValueObject();
xvalue* pList = xrtValueArray();
xrtValueObjectSetNew(pData, XRT_STR_LITERAL("title"),
    xrtValueString(XRT_STR_LITERAL("Страница шаблона")));
for ( i = 0; i < N; i++ )
    xrtValueArrayAppendNew(pList,
        xrtValueString(xrtStrView(arrItem[i])));
xrtValueObjectSetNew(pData, XRT_STR_LITERAL("list"), pList);

/* 2. рендеринг (внутри LoadTemplate: чтение файла → Compile → Render) */
(void)LoadTemplate(pReq, "page.html", pData);

/* 3. освободить дерево данных */
xrtValueRelease(pData);

Синтаксис шаблона (реальный образец template/page.html):

template/page.html (образец синтаксиса)
<h1>{$title}</h1>
<ul>
{#foreach:list}
  <li>{$this}</li>
{/foreach}
</ul>

Что только что произошло: xrtTemplateCompile(xstrview) компилирует исходный текст в xtemplate* (неизменяемый, переиспользуемый); xrtTemplateRender рендерит его деревом xvalue в str. LoadTemplate демо пакует «чтение + компиляция + рендеринг + вывод» в один вызов — весь конвейер на каждый запрос (правка шаблона действует немедленно, ценой перекомпиляции на запрос), либо компилируйте один раз в ServiceInit и кэшируйте (см. 19.4).

19.3 Контролируемые страницы и защита от побега

Файлы каталога page/ недоступны по URL напрямую (статический слой обслуживает только wwwroot). LoadPage скрипта читает через дескриптор xroot, и его оборона общего корня со статическим слоем главы 10:

  • Якорь корня: чтение может приземлиться только внутри каталога page/
  • Отказ .., абсолютным путям и побегу по символическим ссылкам
  • Имя файла из вашего кода, а не из параметров запроса — риск куда ниже; имя из запроса отдаёт границу доверия пользователю (см. продвинутое упражнение)
Доступность по URL: wwwroot/** ✓ напрямую отдаёт статический слой page/** ✗ только через LoadPage (под контролем скрипта) template/** ✗ только через рендеринг LoadTemplate db/** ✗ файловая система (SQLite)

19.4 Стратегии кэширования компиляции

Две стратегии по сценарию:

СтратегияКакПодходит
Конвейер на запросдемо сегодня: LoadTemplate каждый раз читает + компилирует + рендеритразработка (правки шаблона немедленно); редкие страницы
Кэш на ServiceInitCompile в Init и в static; запросы только Renderчастые страницы; шаблоны изолированы по поколениям (смена поколения автоматически перекомпилирует — устаревшего кэша нет)

Стратегия кэширования естественно сцеплена с «статиками по поколениям» из главы 5: закэшированный xtemplate* — состояние поколения, при смене пересоздаётся — проблемы «правил шаблон, а не подействовало» не существует (правка шаблона — изменение контента, работает в связке с reload).

19.5 Контракт

  • Видимость: по URL — только wwwroot; page/template через контролируемое скриптом чтение.
  • Владение: xtemplate* держит компилирующий (Release освобождает); str из Render освобождает вызывающий.
  • Кэширование: закэшированный шаблон — состояние поколения; при смене пересоздаётся, вручную между поколениями не делится.

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

Шаблон положили в wwwroot
Симптом
Пользователи скачивают исходник шаблона (с метками {$}).
Причина
Шаблон — не статический ресурс; место в wwwroot означает публикацию исходников. Шаблоны живут в template/.
Имя файла берётся из параметров запроса
Симптом
LoadPage(pReq, sUserInput) — даже с якорем корня это дырявит границу доверия (перечисление страниц, зондирование существования файлов).
Причина
«Контролируемость» контролируемых страниц — из вашего решения, что читать; отдав решение запросу, вы оставляете только слой антипобега.
✗ Опасно
LoadPage(pReq, sQueryPage);
✓ Правильно
/* отображение по белому списку: запрос выбирает ключ, код — файл */

Упражнения

БазовыйРендеринг списка

Массивом xvalue + foreach отрендерьте страницу с таблицей из 20 строк.

Приёмка: на странице 20 строк; правка шаблона действует немедленно (конвейер на запрос).
ПродвинутыйМаршрут страниц по белому списку

Реализуйте /page/{name}: name через белый список отображается на файл в page/; вне списка — 404.

Приёмка: /page/../secret и /page/unknown дают 404; страницы из списка рендерятся.
ВызовПереключатель кэша шаблонов

Добавьте в LoadTemplate переключатель «компиляция в Init с кэшем» и замерьте нагрузкой разницу RPS двух стратегий.

Приёмка: кэш поднимает RPS ≥ 3 раза; после reload шаблон по-прежнему обновляется.

Итоги

ТезисВывод
Конвейердерево данных (xvalue) → Compile → Render → str
Синтаксисинтерполяция {$var}; циклы {#foreach:list}
Видимостьwwwroot доступен; page/template под контролем скрипта
Кэшированиеконвейер на запрос (разработка) или кэш в Init (продакшн); изоляция поколений против устаревания
Что дальше по xrt