The global contract
Pointers / views in callback parameters (XS_HttpReq, header structs, message views) are valid only within the callback's stack; values may be carried out, pointers must be copied first.
ServiceUnit executes at most once in a generation's lifetime; a candidate whose initialization never completed will never have Unit mis-called.
All 14 symbols are exported as needed. A host that does not export RequestProc is a pure static site; a ws host without WsText/WsBinary answers 503.
Threading at a glance: ServiceInit / ServiceSwap run on the assembly or reload control thread; RequestProc / Ws* / Event* run on engine worker threads (possibly concurrent); ServiceUnit runs on the generation GC thread. Synchronize any cross-thread shared state yourself.
Lifecycle callbacks (3)
ServiceInit lifecycle
Called once after this generation's scripts are loaded — the place to build route tables, open databases, initialize static state.
A new generation becomes visible only after Init completes; inside Init, take the handover data with xsSwapTake (see the XServer API).
static void ServiceInit(XS_HostInfo* pHost)
{
DB_Init(); /* open SQLite, create tables */
Page_Init();
Template_Init();
RouteHTTP_Init(); /* register and compile routes */
}
ServiceUnit lifecycle
The destructor when this generation retires — releases in reverse order of Init. Runs at most once in the whole lifetime.
Two trigger points: the process's graceful-shutdown path, or the return of the old generation's last lease after a hot reload. A generation whose Init never completed will not have Unit called.
static void ServiceUnit(XS_HostInfo* pHost)
{
RouteHTTP_Unit(); /* free patterns and the route table */
Template_Unit();
Page_Unit();
DB_Unit(); /* close the database */
}
ServiceSwap lifecycle
Before a generation swap, export a non-destructive snapshot of cross-generation state; the new generation's ServiceInit takes it back via xsSwapTake.
| Parameter | Direction | Notes |
|---|---|---|
pHost | input | the retiring old-generation host |
ppShared | output | receives the xvalue* snapshot; ownership transfers through the host to the new generation |
What non-destructive means: after exporting the snapshot, the old generation may keep serving (in extreme cases the commit can still be cancelled) — do not close resources inside Swap.
static bool ServiceSwap(XS_HostInfo* pHost, xvalue** ppShared)
{
xvalue* pState = xrtValueObject();
xrtValueObjectSetNew(pState, XRT_STR_LITERAL("counter"),
xrtValueInt64(G_iCounter));
*ppShared = pState;
return true;
}
HTTP callback (1)
RequestProc HTTP
The entry point of every HTTP request. Its three-way return declares "who owns the rest of this request's life".
| Return value | Semantics | What the driver does next |
|---|---|---|
XS_OK (0) | the full response is written | consume the remaining body bytes, continue keep-alive |
XS_FALLBACK (1) | unhandled | GET/HEAD falls to the static layer, the rest gets 404 |
XS_TAKEOVER (2) | take over the whole connection | the driver withdraws; the application sends/receives on its own and finally Closes |
XS_HttpReq is a view struct: exactly one of tcp/tls is non-null (the full xrt stream API is available), head is the parsed request header (with MethodCode), body is the framing state machine, host/server the owners. Field by field in the structs reference.
/* the demo's routing frameworks all converge on RequestProc */
static XS_RequestResult RequestProc(XS_HttpReq* pReq)
{
if ( RouteHTTP_Dispatch(pReq) ) return XS_OK;
return XS_FALLBACK; /* no match -> wwwroot */
}
WebSocket callbacks (6)
XServer performs the handshake validation and xrtWsStreamAttach; masking, fragment reassembly, control frames, and permessage-deflate are all handled in the xrt layer. Sending uses the xrtWsStreamText / Binary families.
WsOpen WebSocket
Fires when the connection is established (101 done, stream attached).
static void WsOpen(XS_HostInfo* pHost, xwsstream* pWs)
{
(void)xrtWsStreamText(pWs, xrtStrView("connected"));
}
WsText WebSocket
Fires after a whole text message is reassembled; tText is a view valid within this callback.
static void WsText(XS_HostInfo* pHost, xwsstream* pWs, xstrview tText)
{
(void)xrtWsStreamText(pWs, tText);
}
WsBinary WebSocket
Fires for a whole binary message; exceeding ws_message_limit cumulatively ends the connection with close 1009.
WsPing WebSocket
A Ping frame arrived. The automatic Pong reply is handled in the xrt layer — this callback is for business observation (heartbeat stats and the like).
WsPong WebSocket
A Pong frame arrived — the place to measure round-trip latency.
WsClose WebSocket
The connection closed; carries the status code and a view of up to 123 bytes of UTF-8 reason.
| Common iCode | Meaning |
|---|---|
1000 | normal closure |
1009 | message over the limit (ws_message_limit) |
1011 | server-internal error (e.g. OOM) |
TCP stream callbacks (3)
XS_StreamConn is a view struct: exactly one of tcp / tls is non-null; use the matching send API on the non-null side (under tcps, bypassing tls to write tcp directly is forbidden). The connection's lifetime is held by the host — the application never calls Destroy.
EventOpen TCP
Fires after a stream connection is established (the moment to send a welcome banner).
static void EventOpen(XS_HostInfo* pHost, XS_StreamConn* pConn)
{
if ( pConn->tcp != NULL )
(void)xrtNetStreamSend(pConn->tcp, "hello\n", 6);
}
EventData TCP
Data arrived. pBuffer is a zero-copy buffer chain (borrowed within the callback); query its size with xrtNetBufSize, consume the data through the xrt buffer APIs.
EventClose TCP
The connection-closed terminal state; includes the network result code and an optional error object (borrowed).
UDP callback (1)
EventDgram UDP
Fires once per datagram; there is no connection state — every packet takes the current generation's lease through the listener slot.
| pMsg field | Notes |
|---|---|
Remote | the source address (convert to text with the xrtNetAddr family) |
Data / Size | payload and length |
Flags | XNET_UDP_MESSAGE_TRUNCATED and friends |
static void EventDgram(XS_HostInfo* pHost, xnetudp* pUdp,
const xnetudpmessage* pMsg)
{
(void)xrtNetUdpSendTo(pUdp, &pMsg->Remote, "ack", 3);
}