Chapter 19 / 51
Volume 3 · Protocol Development

Templates & controlled pages: two weapons of dynamic content

The last piece of server-side rendering: xrtTemplate handles rendering, the page/ directory handles "who may see the output". The demo's /template and /page routes are this chapter's living samples.

Overview

Positioning: the other road besides APIs — the server producing HTML. This chapter also settles a security question: which files are URL-reachable and which must go through scripts. Chapter 10 readers meet "traversal defense" again here.

19.1 Intro

Not all dynamic content deserves a frontend framework: admin panels, report pages and mail rendering are natural fits for server-side templates. XServer's answer keeps a clean split — xrtTemplate (a compiled template engine, 32 APIs) handles "how to render", the page/ directory (outside wwwroot) handles "whether it is directly reachable": templates and controlled pages never surface in the static layer; they only go out through your routes via LoadPage/rendering.

19.2 Template syntax & rendering

The rendering pipeline (the demo's real Handle_Template flow)verified on a real machinesource: demo-single/route_http/basic.h
/* 1. prepare data (an xvalue tree, ch18's ownership rules) */
xvalue* pData = xrtValueObject();
xvalue* pList = xrtValueArray();
xrtValueObjectSetNew(pData, XRT_STR_LITERAL("title"),
    xrtValueString(XRT_STR_LITERAL("Template page")));
for ( i = 0; i < N; i++ )
    xrtValueArrayAppendNew(pList,
        xrtValueString(xrtStrView(arrItem[i])));
xrtValueObjectSetNew(pData, XRT_STR_LITERAL("list"), pList);

/* 2. render (LoadTemplate internally: read file → Compile → Render) */
(void)LoadTemplate(pReq, "page.html", pData);

/* 3. release the data tree */
xrtValueRelease(pData);

Template syntax (a real sample from template/page.html):

template/page.html (syntax sample)
<h1>{$title}</h1>
<ul>
{#foreach:list}
  <li>{$this}</li>
{/foreach}
</ul>

What just happened: xrtTemplateCompile(xstrview) compiles the source text into an xtemplate* (immutable, reusable); xrtTemplateRender renders it with an xvalue data tree into a str. The demo's LoadTemplate packs "read + compile + render + output" into one call — the full pipeline runs per request (template edits apply immediately, at the cost of recompiling per request), or compile once in ServiceInit and cache (see 19.4).

19.3 Controlled pages & escape protection

Files under page/ are not URL-reachable (the static layer only serves wwwroot). The script's LoadPage reads through an xroot handle, with defenses shared with chapter 10's static layer:

  • Root anchoring: reads can only land inside the page/ directory
  • Rejecting .., absolute paths and symlink escapes
  • Filenames coming from your code rather than request parameters are far safer — a request-supplied filename hands the trust boundary to the user (see the advanced exercise)
URL reachability: wwwroot/** ✓ served directly by the static layer page/** ✗ only via LoadPage (script-controlled) template/** ✗ only via LoadTemplate rendering db/** ✗ filesystem (SQLite)

19.4 Compilation caching strategies

Two strategies, chosen by scenario:

StrategyHowFits
Per-request pipelinethe demo today: LoadTemplate reads + compiles + renders each timedevelopment (template edits apply instantly); low-traffic pages
ServiceInit cacheCompile in Init, store in a static; requests only Renderhigh-traffic pages; templates isolated per generation (rebuilt on swap — no stale cache)

The caching strategy meshes with chapter 5's "statics isolated per generation": a cached xtemplate* is generation state, rebuilt on swap — the "edited template not taking effect" staleness problem doesn't exist (editing a template is content change; pair it with reload).

19.5 Contract

  • Visibility: only wwwroot is URL-reachable; page/template go through script-controlled reads.
  • Ownership: the xtemplate* is held by whoever compiles (Release frees it); the str from Render is freed by the caller.
  • Caching: cached templates are generation state — rebuilt on swap, never shared across generations by hand.

Common mistakes

Putting templates into wwwroot
Symptom
Users download the raw template source (with its {$} markers).
Cause
Templates are not static assets — placing them in wwwroot publishes your source. Templates belong in template/.
Filenames taken from request parameters
Symptom
LoadPage(pReq, sUserInput) — even with root anchoring, it punches holes in the trust boundary (enumerating pages, probing file existence).
Cause
The "controlled" of controlled pages comes from you deciding what to read; handing that decision to the request leaves only the anti-escape layer standing.
✗ Risky
LoadPage(pReq, sQueryPage);
✓ Right
/* whitelist mapping: request picks the key, code picks the file */

Exercises

BasicRendering a list page

Render a 20-row data table page with an xvalue array + foreach.

Acceptance: 20 rows on the page; editing the template applies instantly (per-request pipeline).
AdvancedA whitelist page route

Implement /page/{name}: name maps through a whitelist to files under page/; anything else 404.

Acceptance: /page/../secret and /page/unknown both 404; whitelisted pages render.
ChallengeSwitching template caching

Add an "Init-time compile cache" switch to LoadTemplate and load-test the RPS difference between the strategies.

Acceptance: caching lifts RPS ≥ 3×; templates still update after a reload.

Summary

PointConclusion
Pipelinedata tree (xvalue) → Compile → Render → str
Syntax{$var} interpolation; {#foreach:list} loops
Visibilitywwwroot reachable; page/template script-controlled
Cachingper-request pipeline (dev) or Init cache (prod); generation isolation prevents staleness
xrt further reading