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