The Custom knob system: an open world for non-preset fields
Where do xs.json fields the host doesn't preset go? — Untouched into Custom, for the application to read. This "open world" also hosts the http driver's request limits.
Overview
9.1 Intro
Every config system eventually hits this: the framework's preset fields aren't enough. The common fix is to keep adding fields — config bloat and semantic drift follow. XServer's answer is to draw a clear line: preset fields describe host assembly facts (ports, protocols, certificates); every other field is never popped, never validated, kept verbatim — at runtime they hang as xvalue trees on server->Custom, host->Custom and xsConfigRoot(), and the application decides their meaning and validation.
9.2 The Custom read model
/* 1. Root-level Custom: xsConfigRoot returns a retained xvalue; Release it */
xvalue* pRoot = xsConfigRoot();
xvalue* pMy = xrtValueObjectGet(pRoot, "my_app");
xrtValueRelease(pRoot);
/* 2/3. server / host level: view fields hung directly on the info structs (borrowed) */
xvalue* pSrvCustom = pReq->server->Custom;
xvalue* pHostCustom = pReq->host->Custom;
What just happened: the root level needs a Release (you own the reference); server/host Custom are view fields — safe to read inside callbacks, stored only under chapter 3's ownership rules.
The strict parsing helper
The demo's stream module provides XS_CustomReadUInt: it reads a strictly non-negative integer from the Custom tree — a wrong type or a negative value fails instead of guessing a default. Read your knobs with it; avoid lenient conversions.
9.3 The six request-limit knobs
The most important "tenants" of the Custom open world are the request limits consumed by the http/ws drivers (written at the server level; applications may read them too):
| Knob | Default | On exceed |
|---|---|---|
header_limit | — | 431 Request Header Fields Too Large |
path_limit | 2048 | 414 URI Too Long |
body_limit | — | 413 Content Too Large |
idle_timeout | — | silently closes the connection (http/ws/tcp alike) |
ws_message_limit | — | close code 1009 |
ws_protocol | — | handshake 400 (required subprotocol mismatch) |
Why are these Custom and not preset fields? Because they describe "this server's attitude toward requests" — deployment policy rather than assembly facts; and type validation happens at the consumer (e.g. ws_protocol is validated at startup), preserving "whoever consumes validates".
9.4 Contract
- Ownership: xsConfigRoot is retained (pair with Release); server/host Custom are borrowed views.
- Validation: the host never validates Custom fields — the application must parse strictly (the XS_CustomReadUInt pattern) and refuse lenient conversions.
- Naming: use application-prefixed keys (my_app.*) to avoid colliding with the http driver's conventional knobs (header_limit etc.).
Common mistakes
- Symptom
- Every script generation leaks one xvalue reference; the root object count climbs in memory reports.
- Cause
- The documented contract: the return is retained and must be xrtValueRelease'd — a textbook application of chapter 3's ownership rules.
- Symptom
- The config says "body_limt" (typo); the application gets the default 0 and misbehaves silently.
- Cause
- A lenient "missing means zero" read. Custom has no host validation — only strict application-side reads expose typos.
uint32 n = 0;
GetValue(pCustom, "body_limit", &n);
/* missing silently becomes 0 *//* strict read: missing is a config error */Exercises
Add "my_app": {"greeting": "hi"} at the xs.json root; read and print it in the script's ServiceInit.
Set body_limit=100 on the demo, send 99- and 101-byte POSTs and record the status difference; then set path_limit=50 and send a long path.
Write modules/knobs.h: declaratively define application knobs (key / type / default / required), validate all of them strictly at Init and report every missing key at once.
Summary
| Point | Conclusion |
|---|---|
| Custom defined | Non-preset fields kept verbatim; the host never pops or validates them |
| Read entry points | xsConfigRoot (retained) / server->Custom / host->Custom (borrowed) |
| Request limits | header/path/body_limit + idle_timeout + two ws knobs; exceed → 431/414/413/close/1009 |
| Reading discipline | Strict parsing (the XS_CustomReadUInt pattern); application-prefixed keys |
- xrt reference · value — the complete API for accessing the Custom tree