nx_audit_server_routed.nx source
↩ module page · 692 lines · 24935 B
1// nx_audit_server_routed.nx -- multi-endpoint dispatcher daemon.
2//
3// Phase-A route-table integration of the audit-dashboard arc.
4// Composes shipped primitives:
5//
6// nx_http_server bind/listen/accept
7// nx_pages_static static-file serve for /audit/*
8// nx_http_health /health + /ready endpoints
9// nx_http_metrics /metrics endpoint (Prometheus text)
10//
11// Per cardinal NISHI_PRIVACY_BY_DEFAULT (S14): all counters are
12// SYSTEM_PERF (request count, error count, uptime) -- no per-IP,
13// no per-UA, no per-user fields.
14//
15// Per cardinal feedback-bounded-loop-discipline-jpl-rule-2: 1M-
16// request budget per process; supervisor restarts.
17//
18// Route table:
19// GET /health -> nx_http_emit_health (200 or 503)
20// GET /ready -> nx_http_emit_ready
21// GET /metrics -> nx_http_metrics_emit_response (counters)
22// GET /audit/* -> nx_pages_serve_file (base=docs)
23// GET / -> 200 with link list (no implicit index)
24// * -> 404
25//
26// Non-GET methods on any route -> 405 (handled by nx_pages_static
27// for the file paths; this file handles others).
28
29import "nx_syscalls_x86_64.nx"
30import "nx_http_server.nx"
31import "nx_pages_static.nx"
32import "nx_http_health_emit.nx" // the /health EMITTER. Was importing nx_http_health.nx, which since the
33 // 2026-07-21 dedupe is the URL PROBER -- a different organ, with its own
34 // main(), that never carried nx_http_emit_health or the NXHL_* enums.
35import "nx_http_metrics.nx"
36import "nx_log_jsonl.nx"
37import "nx_http_header_find.nx"
38import "nx_http_cache.nx"
39import "nx_http_security_headers.nx"
40import "nx_path_canonicalize.nx"
41const NXAR_MAGIC_2048: i64 = 2048
42const NXAR_MAGIC_1024: i64 = 1024
43const NXAR_MAGIC_2047: i64 = 2047
44
45const NXAR_PORT: i64 = 51848
46const NXAR_REQ_CAP: i64 = 8192
47const NXAR_RESP_CAP: i64 = 524288 // 512 KB
48const NXAR_REQUEST_BUDGET: i64 = 1000000
49// Per-connection keepalive budget. After this many requests on
50// one TCP connection, close + force the client to reconnect (load-
51// balancing fairness + DoS bound).
52const NXAR_PER_CONN_BUDGET: i64 = 100
53
54// ---- Path-prefix matching helper ---------------------------------
55//
56// Returns 1 if path[0..prefix_n] == prefix[0..prefix_n] AND the
57// next byte is `/` or end-of-path (so "/audit" doesn't match
58// "/auditxx" but does match "/audit/index.html").
59
60func nxar_path_starts_with(path: *u8, path_n: i64,
61 prefix: *u8, prefix_n: i64) -> i64 {
62 if path_n < prefix_n { return 0 }
63 var i: i64 = 0
64 while i < prefix_n {
65 if path[i] != prefix[i] { return 0 }
66 i = i + 1
67 }
68 // Boundary: prefix must end at end-of-path OR at a `/`.
69 if path_n == prefix_n { return 1 }
70 if path[prefix_n] == 0x2f as u8 { return 1 }
71 return 0
72}
73
74func nxar_path_eq(path: *u8, path_n: i64,
75 target: *u8, target_n: i64) -> i64 {
76 if path_n != target_n { return 0 }
77 var i: i64 = 0
78 while i < path_n {
79 if path[i] != target[i] { return 0 }
80 i = i + 1
81 }
82 return 1
83}
84
85// ---- Root response (link list) -----------------------------------
86
87func nxar_emit_root(out: *u8, off: *i64, cap: i64) -> i64 {
88 // Simple HTML index pointing at the substrate's served endpoints.
89 let body: *u8 = "<!DOCTYPE html>\n<html><head><title>Nishi</title></head><body><h1>Nishi Audit Dashboard</h1><ul><li><a href=\"/audit/index.html\">audit dashboard</a></li><li><a href=\"/audit/snapshot.json\">snapshot.json</a></li><li><a href=\"/health\">/health</a></li><li><a href=\"/ready\">/ready</a></li><li><a href=\"/metrics\">/metrics</a></li></ul></body></html>\n" as *u8
90 let body_n: i64 = 364
91 // Write status + headers + Content-Length.
92 var p: i64 = *off
93 let hdr: *u8 = "HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=utf-8\r\nContent-Length: " as *u8
94 let hdr_n: i64 = 72
95 var i: i64 = 0
96 while i < hdr_n {
97 if p >= cap { return 1 }
98 out[p] = hdr[i]
99 p = p + 1
100 i = i + 1
101 }
102 // Emit body_n in decimal.
103 let scratch: *u8 = sys_mmap(16)
104 var n: i64 = body_n
105 var k: i64 = 0
106 while n > 0 {
107 scratch[k] = (0x30 + (n - (n / 10) * 10)) as u8
108 n = n / 10
109 k = k + 1
110 }
111 var ri: i64 = k - 1
112 while ri >= 0 {
113 if p >= cap { return 1 }
114 out[p] = scratch[ri]
115 p = p + 1
116 ri = ri - 1
117 }
118 let tail: *u8 = "\r\n\r\n" as *u8
119 var j: i64 = 0
120 while j < 4 {
121 if p >= cap { return 1 }
122 out[p] = tail[j]
123 p = p + 1
124 j = j + 1
125 }
126 var b: i64 = 0
127 while b < body_n {
128 if p >= cap { return 1 }
129 out[p] = body[b]
130 p = p + 1
131 b = b + 1
132 }
133 *off = p
134 return 0
135}
136
137// ---- HTTP method-kind to short name (per nx_http_io enum) --------
138// 1 = GET, 2 = POST, 3 = OPTIONS, 0 = unknown
139
140func nxar_method_name(k: i64) -> *u8 {
141 if k == 1 { return "GET" as *u8 }
142 if k == 2 { return "POST" as *u8 }
143 if k == 3 { return "OPTIONS" as *u8 }
144 return "?" as *u8
145}
146
147func nxar_method_name_len(k: i64) -> i64 {
148 if k == 1 { return 3 }
149 if k == 2 { return 4 }
150 if k == 3 { return 7 }
151 return 1
152}
153
154// ---- Per-request structured log emitter --------------------------
155//
156// Emits one JSON line to stdout per request. SYSTEM_PERF + SYSTEM_
157// STATE category only per S14 -- explicitly excludes IP, UA,
158// cookies, body bytes, query string (which CAN contain PII like
159// session tokens), client identity.
160
161func nxar_log_request(
162 ts_ms: i64,
163 method_kind: i64,
164 path: *u8, path_n: i64,
165 status: i64,
166 duration_ms: i64,
167 resp_bytes: i64) -> i64 {
168 let log_buf: *u8 = sys_mmap(NXAR_MAGIC_2048)
169 let log_off: *i64 = sys_mmap(8) as *i64
170 log_off[0] = 0
171 let scratch: *u8 = sys_mmap(64)
172
173 // Build msg = "<method> <path>" with caller-bounded path length.
174 let method_str: *u8 = nxar_method_name(method_kind)
175 let method_n: i64 = nxar_method_name_len(method_kind)
176 let msg_buf: *u8 = sys_mmap(512)
177 var msg_off: i64 = 0
178 var i: i64 = 0
179 while i < method_n {
180 msg_buf[msg_off + i] = method_str[i]
181 i = i + 1
182 }
183 msg_off = msg_off + method_n
184 msg_buf[msg_off] = 0x20 as u8
185 msg_off = msg_off + 1
186 // Cap path at 256 bytes for log brevity.
187 var cap_path: i64 = path_n
188 if cap_path > 256 { cap_path = 256 }
189 var j: i64 = 0
190 while j < cap_path {
191 msg_buf[msg_off + j] = path[j]
192 j = j + 1
193 }
194 msg_off = msg_off + cap_path
195
196 // Pick level by status class.
197 var level: i64 = NXL_INFO
198 if status >= 400 && status <= 499 { level = NXL_WARN }
199 if status >= 500 && status <= 599 { level = NXL_ERROR }
200
201 // Emit base line + status field. Compose: emit_line_with_int
202 // gives us status; we need ALSO duration + bytes. Pragmatic
203 // v0: emit two log lines, one with status + duration_ms and one
204 // with bytes. Future: nx_log_emit_line_with_multi (queued).
205 //
206 // v0 emit: single line with status field.
207 nx_log_emit_line_with_int(log_buf, log_off, NXAR_MAGIC_2048,
208 ts_ms,
209 level,
210 "nx_audit_server" as *u8, 15,
211 msg_buf, msg_off,
212 "status" as *u8, 6,
213 status,
214 scratch)
215
216 // Write to stdout (fd=1).
217 sys_write(1, log_buf, log_off[0])
218 return 0
219}
220
221// ---- Plain 404 emitter -------------------------------------------
222
223func nxar_emit_404(out: *u8, off: *i64, cap: i64) -> i64 {
224 let resp: *u8 = "HTTP/1.1 404 Not Found\r\nContent-Type: text/plain\r\nContent-Length: 13\r\n\r\nROUTED-404\r\n\r\n" as *u8
225 let n: i64 = 87
226 var i: i64 = 0
227 var p: i64 = *off
228 while i < n {
229 if p >= cap { return 1 }
230 out[p] = resp[i]
231 p = p + 1
232 i = i + 1
233 }
234 *off = p
235 return 0
236}
237
238// ---- Metric-table builder ----------------------------------------
239//
240// The /metrics endpoint exposes 4 counters (all SYSTEM_PERF /
241// SYSTEM_STATE per S14 privacy invariant; no per-user fields):
242//
243// nishi_uptime_ms gauge
244// nishi_requests_total counter
245// nishi_errors_total counter
246// nishi_request_budget_remaining gauge
247
248const N_METRICS: i64 = 4
249
250func nxar_serve_metrics(out: *u8, out_cap: i64, out_n: *i64,
251 uptime_ms: i64, n_requests: i64,
252 n_errors: i64, budget_remaining: i64) -> i64 {
253 let names_buf: *u8 = sys_mmap(512)
254 let name_offs: *i64 = sys_mmap(32) as *i64
255 let name_lens: *i64 = sys_mmap(32) as *i64
256 let helps_buf: *u8 = sys_mmap(512)
257 let help_offs: *i64 = sys_mmap(32) as *i64
258 let help_lens: *i64 = sys_mmap(32) as *i64
259 let types: *i64 = sys_mmap(32) as *i64
260 let values: *i64 = sys_mmap(32) as *i64
261
262 // Names
263 let n0: *u8 = "nishi_uptime_ms" as *u8
264 let n1: *u8 = "nishi_requests_total" as *u8
265 let n2: *u8 = "nishi_errors_total" as *u8
266 let n3: *u8 = "nishi_request_budget_remaining" as *u8
267 var i: i64 = 0
268 while i < 15 { names_buf[i] = n0[i]; i = i + 1 }
269 name_offs[0] = 0; name_lens[0] = 15
270 var j: i64 = 0
271 while j < 20 { names_buf[15 + j] = n1[j]; j = j + 1 }
272 name_offs[1] = 15; name_lens[1] = 20
273 var k: i64 = 0
274 while k < 18 { names_buf[35 + k] = n2[k]; k = k + 1 }
275 name_offs[2] = 35; name_lens[2] = 18
276 var m: i64 = 0
277 while m < 30 { names_buf[53 + m] = n3[m]; m = m + 1 }
278 name_offs[3] = 53; name_lens[3] = 30
279
280 // Helps (short; permissible per Prometheus spec).
281 let h0: *u8 = "Daemon uptime in ms" as *u8
282 let h1: *u8 = "Total HTTP requests served" as *u8
283 let h2: *u8 = "Total request errors" as *u8
284 let h3: *u8 = "Remaining request budget before clean exit" as *u8
285 var ha: i64 = 0
286 while ha < 19 { helps_buf[ha] = h0[ha]; ha = ha + 1 }
287 help_offs[0] = 0; help_lens[0] = 19
288 var hb: i64 = 0
289 while hb < 26 { helps_buf[19 + hb] = h1[hb]; hb = hb + 1 }
290 help_offs[1] = 19; help_lens[1] = 26
291 var hc: i64 = 0
292 while hc < 20 { helps_buf[45 + hc] = h2[hc]; hc = hc + 1 }
293 help_offs[2] = 45; help_lens[2] = 20
294 var hd: i64 = 0
295 while hd < 42 { helps_buf[65 + hd] = h3[hd]; hd = hd + 1 }
296 help_offs[3] = 65; help_lens[3] = 42
297
298 types[0] = NXM_TYPE_GAUGE
299 types[1] = NXM_TYPE_COUNTER
300 types[2] = NXM_TYPE_COUNTER
301 types[3] = NXM_TYPE_GAUGE
302 values[0] = uptime_ms
303 values[1] = n_requests
304 values[2] = n_errors
305 values[3] = budget_remaining
306
307 let scratch: *u8 = sys_mmap(64)
308 return nx_http_metrics_emit_response(out, out_cap, out_n,
309 names_buf, name_offs, name_lens,
310 helps_buf, help_offs, help_lens,
311 types, values, N_METRICS,
312 scratch)
313}
314
315// ---- Health serve (System-class only) ----------------------------
316//
317// PASS so long as the daemon is up and the request budget has
318// headroom (>10% remaining). FAIL when budget exhausted.
319
320func nxar_serve_health(out: *u8, out_cap: i64, out_n: *i64,
321 uptime_ms: i64, budget_remaining: i64) -> i64 {
322 var status: i64 = NXHL_STATUS_PASS
323 let warn_thresh: i64 = NXAR_REQUEST_BUDGET / 10
324 if budget_remaining < warn_thresh { status = NXHL_STATUS_WARN }
325 if budget_remaining < 100 { status = NXHL_STATUS_FAIL }
326 let scratch: *u8 = sys_mmap(64)
327 return nx_http_emit_health(out, out_cap, out_n,
328 status, uptime_ms, 1, scratch)
329}
330
331// ---- S-class /audit/* serve with cache + security headers -------
332//
333// Replaces the routed daemon's plain nx_pages_serve_file call for
334// /audit/* paths with a full production-grade flow:
335//
336// 1. parse If-None-Match from request headers
337// 2. canonicalize the URL path (CWE-22 prevention)
338// 3. join with base dir
339// 4. read file body
340// 5. compute ETag (FNV-1a 64 hex) over the body
341// 6. if If-None-Match matches -> emit 304 (113 bytes; saves ~99% bandwidth)
342// 7. else emit 200 with: ETag + Cache-Control + 7 security headers + body
343//
344// Returns the number of bytes written to resp_buf + sets *served_304
345// = 1 if the request hit the 304 fast-path.
346
347func nxar_serve_audit_cached(
348 req_buf: *u8, headers_end: i64,
349 url_path: *u8, url_n: i64,
350 base_dir: *u8, base_n: i64,
351 resp_buf: *u8, resp_cap: i64,
352 served_304: *i64) -> i64 {
353 *served_304 = 0
354
355 // Step 1: If-None-Match.
356 let inm_off: *i64 = sys_mmap(8) as *i64
357 let inm_len: *i64 = sys_mmap(8) as *i64
358 let inm_rc: i64 = nx_http_header_find(req_buf, headers_end,
359 "If-None-Match" as *u8, 13,
360 inm_off, inm_len)
361
362 // Step 2: canonicalize. Strip leading `/`.
363 var off: i64 = 0
364 if url_n < 2 {
365 // Bare `/` -- not allowed; fallthrough to 404.
366 let r404: *u8 = "HTTP/1.1 404 Not Found\r\nContent-Type: text/plain\r\nContent-Length: 13\r\n\r\nROUTED-404\r\n\r\n" as *u8
367 var rn: i64 = 0
368 while rn < 87 {
369 if off >= resp_cap { return -1 }
370 resp_buf[off] = r404[rn]
371 off = off + 1
372 rn = rn + 1
373 }
374 return off
375 }
376 let rel_ptr: *u8 = ((url_path as i64) + 1) as *u8
377 let rel_n: i64 = url_n - 1
378 let canon_buf: *u8 = sys_mmap(NXAR_MAGIC_1024)
379 let canon_n: *i64 = sys_mmap(8) as *i64
380 let cv: i64 = nx_path_canonicalize(rel_ptr, rel_n, canon_buf, NXAR_MAGIC_1024, canon_n)
381 if cv != NXP_OK {
382 // Refuse. Emit 403 / 404.
383 let r403: *u8 = "HTTP/1.1 403 Forbidden\r\nContent-Type: text/plain\r\nContent-Length: 10\r\n\r\nForbidden\n" as *u8
384 var rn3: i64 = 0
385 while rn3 < 84 {
386 if off >= resp_cap { return -1 }
387 resp_buf[off] = r403[rn3]
388 off = off + 1
389 rn3 = rn3 + 1
390 }
391 return off
392 }
393
394 // Step 3: join with base.
395 let full_path: *u8 = sys_mmap(NXAR_MAGIC_2048)
396 let full_n: *i64 = sys_mmap(8) as *i64
397 let jv: i64 = nx_path_join(base_dir, base_n, canon_buf, canon_n[0],
398 full_path, NXAR_MAGIC_2047, full_n)
399 if jv != NXP_OK { return -1 }
400 full_path[full_n[0]] = 0 as u8
401
402 // Step 4: read body.
403 let body_n_p: *i64 = sys_mmap(8) as *i64
404 let body: *u8 = sys_read_file_x86_64(full_path, body_n_p)
405 if body == (0 as *u8) {
406 // 404
407 let r404b: *u8 = "HTTP/1.1 404 Not Found\r\nContent-Type: text/plain\r\nContent-Length: 13\r\n\r\nROUTED-404\r\n\r\n" as *u8
408 var rn4: i64 = 0
409 while rn4 < 87 {
410 if off >= resp_cap { return -1 }
411 resp_buf[off] = r404b[rn4]
412 off = off + 1
413 rn4 = rn4 + 1
414 }
415 return off
416 }
417 let body_n: i64 = body_n_p[0]
418
419 // Step 5: compute ETag.
420 let etag_buf: *u8 = sys_mmap(32)
421 let etag_off: *i64 = sys_mmap(8) as *i64
422 etag_off[0] = 0
423 nx_http_etag_compute(body, body_n, etag_buf, etag_off, 32)
424 let etag_n: i64 = etag_off[0]
425
426 // Step 6: If-None-Match check.
427 if inm_rc == NXHF_FOUND {
428 let inm_ptr: *u8 = ((req_buf as i64) + inm_off[0]) as *u8
429 let decision: i64 = nx_http_cache_decide(inm_ptr, inm_len[0],
430 etag_buf, etag_n)
431 if decision == NXC_OK_NOT_MODIFIED {
432 // 304: ~113 bytes total
433 let r304_off: *i64 = sys_mmap(8) as *i64
434 r304_off[0] = 0
435 nx_http_emit_304(resp_buf, r304_off, resp_cap,
436 etag_buf, etag_n, 300)
437 *served_304 = 1
438 return r304_off[0]
439 }
440 }
441
442 // Step 7: emit 200 with full S-class header set.
443 // Detect MIME from canonical path.
444 let mime: i64 = nx_pages_mime_from_path(canon_buf, canon_n[0])
445
446 // Status + Content-Type
447 let h1: *u8 = "HTTP/1.1 200 OK\r\nContent-Type: " as *u8
448 var hi: i64 = 0
449 while h1[hi] != 0 {
450 if off >= resp_cap { return -1 }
451 resp_buf[off] = h1[hi]
452 off = off + 1
453 hi = hi + 1
454 }
455 let mime_str: *u8 = nx_pages_mime_type(mime)
456 let mime_n: i64 = nx_pages_mime_type_len(mime)
457 var mi: i64 = 0
458 while mi < mime_n {
459 if off >= resp_cap { return -1 }
460 resp_buf[off] = mime_str[mi]
461 off = off + 1
462 mi = mi + 1
463 }
464 if off + 2 > resp_cap { return -1 }
465 resp_buf[off] = 0x0d as u8; off = off + 1
466 resp_buf[off] = 0x0a as u8; off = off + 1
467
468 // ETag
469 let et_pre: *u8 = "ETag: \"" as *u8
470 var ei: i64 = 0
471 while et_pre[ei] != 0 {
472 if off >= resp_cap { return -1 }
473 resp_buf[off] = et_pre[ei]
474 off = off + 1
475 ei = ei + 1
476 }
477 var eb: i64 = 0
478 while eb < etag_n {
479 if off >= resp_cap { return -1 }
480 resp_buf[off] = etag_buf[eb]
481 off = off + 1
482 eb = eb + 1
483 }
484 let et_post: *u8 = "\"\r\n" as *u8
485 var ep: i64 = 0
486 while et_post[ep] != 0 {
487 if off >= resp_cap { return -1 }
488 resp_buf[off] = et_post[ep]
489 off = off + 1
490 ep = ep + 1
491 }
492
493 // Cache-Control + Security headers (bundled)
494 let extras_off: *i64 = sys_mmap(8) as *i64
495 extras_off[0] = off
496 nx_http_emit_cache_control_header(resp_buf, extras_off, resp_cap, 300)
497 nx_http_emit_security_headers_bundle(resp_buf, extras_off, resp_cap, NXSH_STRICT)
498 off = extras_off[0]
499
500 // Content-Length: <body_n>\r\n
501 let cl_pre: *u8 = "Content-Length: " as *u8
502 var ci: i64 = 0
503 while cl_pre[ci] != 0 {
504 if off >= resp_cap { return -1 }
505 resp_buf[off] = cl_pre[ci]
506 off = off + 1
507 ci = ci + 1
508 }
509 // Decimal body_n
510 var bn: i64 = body_n
511 if bn == 0 {
512 if off >= resp_cap { return -1 }
513 resp_buf[off] = 0x30 as u8
514 off = off + 1
515 } else {
516 let dig_start: i64 = off
517 while bn > 0 {
518 if off >= resp_cap { return -1 }
519 resp_buf[off] = (0x30 + (bn - (bn / 10) * 10)) as u8
520 off = off + 1
521 bn = bn / 10
522 }
523 var lo: i64 = dig_start
524 var hi2: i64 = off - 1
525 while lo < hi2 {
526 let tmp: i64 = resp_buf[lo] as i64
527 resp_buf[lo] = resp_buf[hi2]
528 resp_buf[hi2] = tmp as u8
529 lo = lo + 1
530 hi2 = hi2 - 1
531 }
532 }
533 if off + 4 > resp_cap { return -1 }
534 resp_buf[off] = 0x0d as u8
535 resp_buf[off + 1] = 0x0a as u8
536 resp_buf[off + 2] = 0x0d as u8
537 resp_buf[off + 3] = 0x0a as u8
538 off = off + 4
539
540 // Body
541 var bi: i64 = 0
542 while bi < body_n {
543 if off >= resp_cap { return -1 }
544 resp_buf[off] = body[bi]
545 off = off + 1
546 bi = bi + 1
547 }
548 return off
549}
550
551// ---- Main daemon loop --------------------------------------------
552
553func main() -> i64 {
554 // Bind 127.0.0.1:51847
555 let addr: *u8 = sys_mmap(16)
556 let a_rc: i64 = nx_http_server_addr_loopback(addr, NXAR_PORT)
557 if a_rc != 16 { return 10 }
558
559 let lv: *i64 = sys_mmap(8) as *i64
560 let lfd: i64 = nx_http_server_listen(addr, 64, lv)
561 if lfd < 0 { return 20 + lv[0] }
562
563 // Start clock (monotonic ms).
564 let t_start: i64 = sys_now_ms()
565
566 // Internal SYSTEM_PERF counters per S14 (no PEOPLE_* fields).
567 var n_requests: i64 = 0
568 var n_errors: i64 = 0
569 var n_served: i64 = 0
570
571 let base_dir: *u8 = "docs" as *u8
572 let base_dir_n: i64 = 4
573
574 while n_served < NXAR_REQUEST_BUDGET {
575 let av: *i64 = sys_mmap(8) as *i64
576 let cfd: i64 = nx_http_server_accept_one(lfd, av)
577 if cfd < 0 {
578 n_errors = n_errors + 1
579 n_served = n_served + 1
580 if n_served >= NXAR_REQUEST_BUDGET { return 0 }
581 }
582
583 if cfd >= 0 {
584 // HTTP/1.1 keepalive: read multiple requests per accepted
585 // TCP connection up to NXAR_PER_CONN_BUDGET. Browser
586 // pages reuse one TCP connection for HTML + linked
587 // assets; nginx + WordPress + every other production
588 // stack does this. Substrate's parity.
589 var reqs_on_conn: i64 = 0
590 var conn_alive: i64 = 1
591 while conn_alive == 1 && reqs_on_conn < NXAR_PER_CONN_BUDGET && n_served < NXAR_REQUEST_BUDGET {
592 let req_buf: *u8 = sys_mmap(NXAR_REQ_CAP)
593 let om: *i64 = sys_mmap(8) as *i64
594 let opo: *i64 = sys_mmap(8) as *i64
595 let opl: *i64 = sys_mmap(8) as *i64
596 let ocl: *i64 = sys_mmap(8) as *i64
597 let obo: *i64 = sys_mmap(8) as *i64
598 let orn: *i64 = sys_mmap(8) as *i64
599
600 let rrc: i64 = nx_http_server_read_request(cfd, req_buf, NXAR_REQ_CAP,
601 om, opo, opl, ocl, obo, orn)
602 if rrc != NXS_OK {
603 // EOF or parse error -- client closed or bad request.
604 conn_alive = 0
605 }
606 if rrc == NXS_OK {
607 let path_ptr: *u8 = ((req_buf as i64) + opo[0]) as *u8
608 let path_n: i64 = opl[0]
609 let resp_buf: *u8 = sys_mmap(NXAR_RESP_CAP)
610 let resp_n: *i64 = sys_mmap(8) as *i64
611 resp_n[0] = 0
612 var off: i64 = 0
613
614 let now_ms: i64 = sys_now_ms()
615 let uptime_ms: i64 = now_ms - t_start
616 let budget_remaining: i64 = NXAR_REQUEST_BUDGET - n_served
617
618 // Dispatch.
619 var handled: i64 = 0
620 if nxar_path_eq(path_ptr, path_n, "/health" as *u8, 7) == 1 {
621 nxar_serve_health(resp_buf, NXAR_RESP_CAP, resp_n,
622 uptime_ms, budget_remaining)
623 handled = 1
624 }
625 if handled == 0 {
626 if nxar_path_eq(path_ptr, path_n, "/ready" as *u8, 6) == 1 {
627 nxar_serve_health(resp_buf, NXAR_RESP_CAP, resp_n,
628 uptime_ms, budget_remaining)
629 handled = 1
630 }
631 }
632 if handled == 0 {
633 if nxar_path_eq(path_ptr, path_n, "/metrics" as *u8, 8) == 1 {
634 nxar_serve_metrics(resp_buf, NXAR_RESP_CAP, resp_n,
635 uptime_ms, n_requests, n_errors,
636 budget_remaining)
637 handled = 1
638 }
639 }
640 if handled == 0 {
641 if nxar_path_starts_with(path_ptr, path_n, "/audit" as *u8, 6) == 1 {
642 // S-class flow: ETag + Cache-Control +
643 // security headers + If-None-Match -> 304.
644 let s304: *i64 = sys_mmap(8) as *i64
645 let new_n: i64 = nxar_serve_audit_cached(
646 req_buf, obo[0],
647 path_ptr, path_n,
648 base_dir, base_dir_n,
649 resp_buf, NXAR_RESP_CAP,
650 s304)
651 if new_n > 0 { resp_n[0] = new_n }
652 handled = 1
653 }
654 }
655 if handled == 0 {
656 if nxar_path_eq(path_ptr, path_n, "/" as *u8, 1) == 1 {
657 nxar_emit_root(resp_buf, &off, NXAR_RESP_CAP)
658 resp_n[0] = off
659 handled = 1
660 }
661 }
662 if handled == 0 {
663 nxar_emit_404(resp_buf, &off, NXAR_RESP_CAP)
664 resp_n[0] = off
665 n_errors = n_errors + 1
666 }
667
668 // Keepalive variant: don't close fd here so we can
669 // serve another request on the same connection.
670 let send_rc: i64 = nx_http_server_send_response_nokeep_close(
671 cfd, resp_buf, resp_n[0])
672 if send_rc != NXS_OK { conn_alive = 0 }
673 n_requests = n_requests + 1
674
675 // SYSTEM-class request log (no IP / UA / user data per S14).
676 var log_status: i64 = 200
677 if handled == 0 { log_status = 404 }
678 let req_end_ms: i64 = sys_now_ms()
679 let req_dur: i64 = req_end_ms - now_ms
680 nxar_log_request(now_ms, om[0], path_ptr, path_n,
681 log_status, req_dur, resp_n[0])
682 }
683 n_served = n_served + 1
684 reqs_on_conn = reqs_on_conn + 1
685 } // close inner keepalive loop
686 sys_close(cfd)
687 }
688 }
689
690 sys_close(lfd)
691 return 0
692}