Шаблоны и контролируемые страницы: два оружия динамического контента
Последний пазл серверного рендеринга: xrtTemplate отвечает за рендеринг, каталог page/ — за «видимость результата». Маршруты /template и /page демо — живые образцы главы.
Обзор
19.1 Введение
Не весь динамический контент заслуживает фронтенд-фреймворк: админ-панели, страницы отчётов и рендеринг писем естественно ложатся на серверные шаблоны. Ответ XServer держит разделение чистым — xrtTemplate (компилируемый шаблонизатор, 32 API) отвечает «как рендерить», а каталог page/ (вне wwwroot) — «можно ли получить напрямую»: шаблоны и контролируемые страницы не видны статическому слою и выходят только через ваши маршруты LoadPage/рендеринга.
19.2 Синтаксис шаблонов и рендеринг
/* 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):
<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/
- Отказ
.., абсолютным путям и побегу по символическим ссылкам - Имя файла из вашего кода, а не из параметров запроса — риск куда ниже; имя из запроса отдаёт границу доверия пользователю (см. продвинутое упражнение)
19.4 Стратегии кэширования компиляции
Две стратегии по сценарию:
| Стратегия | Как | Подходит |
|---|---|---|
| Конвейер на запрос | демо сегодня: LoadTemplate каждый раз читает + компилирует + рендерит | разработка (правки шаблона немедленно); редкие страницы |
| Кэш на ServiceInit | Compile в Init и в static; запросы только Render | частые страницы; шаблоны изолированы по поколениям (смена поколения автоматически перекомпилирует — устаревшего кэша нет) |
Стратегия кэширования естественно сцеплена с «статиками по поколениям» из главы 5: закэшированный xtemplate* — состояние поколения, при смене пересоздаётся — проблемы «правил шаблон, а не подействовало» не существует (правка шаблона — изменение контента, работает в связке с reload).
19.5 Контракт
- Видимость: по URL — только wwwroot; page/template через контролируемое скриптом чтение.
- Владение: xtemplate* держит компилирующий (Release освобождает); str из Render освобождает вызывающий.
- Кэширование: закэшированный шаблон — состояние поколения; при смене пересоздаётся, вручную между поколениями не делится.
Частые ошибки
- Симптом
- Пользователи скачивают исходник шаблона (с метками {$}).
- Причина
- Шаблон — не статический ресурс; место в wwwroot означает публикацию исходников. Шаблоны живут в template/.
- Симптом
- LoadPage(pReq, sUserInput) — даже с якорем корня это дырявит границу доверия (перечисление страниц, зондирование существования файлов).
- Причина
- «Контролируемость» контролируемых страниц — из вашего решения, что читать; отдав решение запросу, вы оставляете только слой антипобега.
LoadPage(pReq, sQueryPage);/* отображение по белому списку: запрос выбирает ключ, код — файл */Упражнения
Массивом xvalue + foreach отрендерьте страницу с таблицей из 20 строк.
Реализуйте /page/{name}: name через белый список отображается на файл в page/; вне списка — 404.
/page/../secret и /page/unknown дают 404; страницы из списка рендерятся.Добавьте в LoadTemplate переключатель «компиляция в Init с кэшем» и замерьте нагрузкой разницу RPS двух стратегий.
Итоги
| Тезис | Вывод |
|---|---|
| Конвейер | дерево данных (xvalue) → Compile → Render → str |
| Синтаксис | интерполяция {$var}; циклы {#foreach:list} |
| Видимость | wwwroot доступен; page/template под контролем скрипта |
| Кэширование | конвейер на запрос (разработка) или кэш в Init (продакшн); изоляция поколений против устаревания |
- справочник xrt · template — Compile/Render и конфигурация рендеринга
- справочник xrt · html — инструменты экранирования HTML (безопасный вывод шаблонов)