code wiki / (root) / nx_audit_server_routed.nx

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}