code wiki / (root) / nx_lib_httpd.nx

nx_lib_httpd.nx source

↩ module page · 141 lines · 7403 B

1// nx_lib_httpd.nx -- sovereign HTTP DAEMON for the nishi-library read API. 2// Serves the READ PATH over the wire straight from nx_lib_store, RETIRING the 3// Python serve.py endpoints + its SQLite. No python, no 3rd-party HTTP stack, 4// no shell: raw sovereign sockets (nx_syscalls, pulled via nx_lib_serve -- so 5// we do NOT import nx_syscalls_x86_64.nx = no sys_* double-emit) + the pure 6// router nx_lib_http. 7// GET /api/works -> JSON array of work_hks 8// GET /api/work/<hk> -> JSON work record (200) or 404 9// license_tier: ORIGINAL | genealogy_id: nishi_library_sovereign_httpd_2026_06_30 10import "nx_lib_http.nx" 11 12const LHD_AF_INET: i64 = 2 13const LHD_SOCK_STREAM: i64 = 1 14const LHD_SOL_SOCKET: i64 = 1 15const LHD_SO_REUSEADDR: i64 = 2 16const LHD_PORT: i64 = 8095 17const LHD_BACKLOG: i64 = 128 18const LHD_BUDGET: i64 = 1000000 // JPL-rule-2 bounded accept loop 19 20func lhd_write_all(fd: i64, buf: *u8, n: i64) -> i64 { 21 var off: i64 = 0 22 while off < n { 23 let p: *u8 = ((buf as i64) + off) as *u8 24 let w: i64 = sys_write(fd, p, n - off) 25 if w <= 0 { return 0 } 26 off = off + w 27 } 28 return 0 29} 30 31// read one request off cfd, route via nx_lib_http, write the response. 32// GET -> read routes; POST /api/ingest -> write route. 33// ★★★★★★PER-REQUEST ALLOCATION IN A NON-FORKING ACCEPT LOOP IS THE DOCUMENTED SEGFAULT PATH. 34// main() below is `while served < LHD_BUDGET { cfd = sys_accept(fd); lhd_handle(cfd) }` with NO fork, 35// so this function runs in the LONG-LIVED PARENT. It used to mmap 131,072 + 1,200,000 bytes on every 36// request and free neither: ~1.33 MiB and TWO kernel VMAs per request, against its own declared budget 37// of 1,000,000 requests. nx_syscalls states the consequence exactly -- "vm.max_map_count defaults to 38// 65530, after which mmap returns -ENOMEM and callers write through the failed pointer" -- so this 39// daemon would have died around request ~32,765, far short of the budget it advertises. 40// The early `if n < 5 { return 0 }` leaked the request buffer on its own. 41// FIX: the daemon is single-threaded and does not fork, so exactly one request is in flight at a time 42// and these are fixed-size SCRATCH. They now live ONCE for the process instead of once per request -- 43// ★NEVER ALLOCATE IN A HOT LOOP; HOIST THE BUFFER. Zero allocation per request, no signature change, 44// and every early return is safe by construction because there is nothing left to free. 45// ⚠THE SIZES WERE INLINE MAGIC NUMBERS AND ARE NOW NAMED. 46// 🔴CORRECTION 2026-08-16 -- THIS NOTE NAMED `LHD_OUT_CAP` AS THE HAZARD AND THAT IS THE SAFE BUFFER. 47// Re-derived: lhd_response copies a ~120 B header plus `bn` into `out`, and `bn` comes from a 1 MiB 48// `body`, so out needs <= 1,048,696 of its 1,200,000 -- PROVABLY ADEQUATE. 49// ★★★★★★A HAZARD NOTE THAT NAMES THE WRONG BUFFER IS WORSE THAN NO NOTE: IT SPENDS THE NEXT READER'S 50// ATTENTION ON THE ONE PLACE THAT CANNOT FAIL. 51// The REAL unchecked writes were one level down in nx_lib_http.lhd_response: THREE fixed 2048/2048/1024 52// buffers copied from the request path, while path_n is bounded only by LHD_REQ_CAP below -- remotely 53// triggerable by a long query string. Fixed there by DERIVING each from path_n. This daemon is not in the 54// live service table, so that was latent, not live -- and it is exactly why it had to be fixed BEFORE any 55// promote, since "built but unpromoted" was its standing residual. 56// ⚠STILL OPEN, NAMED NOT SILENT: nx_lib_serve_list / nx_lib_index_search / nx_lib_serve_work each write 57// into that 1 MiB `body` with NO capacity argument. It cannot be derived here (the size depends on the 58// store), so the fix is a capacity PARAMETER on all three -- a signature change across nx_lib_serve and 59// nx_lib_index, deliberately not attempted in this edit. 60const LHD_REQ_CAP: i64 = 131072 61const LHD_OUT_CAP: i64 = 1200000 62static LHD_REQ_BUF: i64 63func lhd_reqbuf() -> *u8 { if LHD_REQ_BUF == 0 { LHD_REQ_BUF = sys_mmap(LHD_REQ_CAP) as i64 } return LHD_REQ_BUF as *u8 } 64static LHD_OUT_BUF: i64 65func lhd_outbuf() -> *u8 { if LHD_OUT_BUF == 0 { LHD_OUT_BUF = sys_mmap(LHD_OUT_CAP) as i64 } return LHD_OUT_BUF as *u8 } 66 67func lhd_handle(cfd: i64) -> i64 { 68 let req: *u8 = lhd_reqbuf() 69 let n: i64 = sys_read(cfd, req, LHD_REQ_CAP) 70 if n < 5 { return 0 } 71 let out: *u8 = lhd_outbuf() 72 let m0: i64 = req[0] as i64 73 if m0 == 71 { // 'G' GET ("GET " -> path at 4) 74 let path: *u8 = ((req as i64) + 4) as *u8 75 var pe: i64 = 4 76 var path_end: i64 = 0 - 1 77 while pe < n { if path_end < 0 { if (req[pe] as i64) == 32 { path_end = pe } } pe = pe + 1 } 78 if path_end < 0 { path_end = n } 79 let rn: i64 = lhd_response(path, path_end - 4, out) 80 lhd_write_all(cfd, out, rn) 81 } else { if m0 == 80 { // 'P' POST ("POST " -> path at 5) 82 let path: *u8 = ((req as i64) + 5) as *u8 83 var pe: i64 = 5 84 var path_end: i64 = 0 - 1 85 while pe < n { if path_end < 0 { if (req[pe] as i64) == 32 { path_end = pe } } pe = pe + 1 } 86 if path_end < 0 { path_end = n } 87 let path_n: i64 = path_end - 5 88 // body starts after CRLF CRLF 89 var bs: i64 = 0 - 1 90 var bi: i64 = 0 91 while bi + 3 < n { 92 if bs < 0 { if req[bi] == 13 as u8 { if req[bi+1] == 10 as u8 { if req[bi+2] == 13 as u8 { if req[bi+3] == 10 as u8 { bs = bi + 4 } } } } } 93 bi = bi + 1 94 } 95 if lhd_starts(path, path_n, "/api/ingest" as *u8) == 1 { 96 var bptr: *u8 = ("" as *u8) 97 var blen: i64 = 0 98 if bs >= 0 { bptr = ((req as i64) + bs) as *u8; blen = n - bs } 99 let rn: i64 = lhd_ingest_response(bptr, blen, out) 100 lhd_write_all(cfd, out, rn) 101 } else { 102 let rn: i64 = lhd_response("/nf" as *u8, 3, out) 103 lhd_write_all(cfd, out, rn) 104 } 105 } } 106 return 0 107} 108 109// sockaddr_in (16 bytes) for 0.0.0.0:<port>, port in network (big-endian) order. 110func lhd_sockaddr(addr: *u8, port: i64) -> i64 { 111 addr[0] = 2 as u8 // AF_INET family low byte 112 addr[1] = 0 as u8 113 addr[2] = ((port / 256) & 255) as u8 // port hi (network order) 114 addr[3] = (port & 255) as u8 // port lo 115 var i: i64 = 4 116 while i < 16 { addr[i] = 0 as u8; i = i + 1 } // INADDR_ANY + zero pad 117 return 0 118} 119 120func main() -> i64 { 121 let fd: i64 = sys_socket(LHD_AF_INET, LHD_SOCK_STREAM, 0) 122 if fd < 0 { return 10 } 123 let one: *u8 = sys_mmap(4) 124 one[0] = 1 as u8; one[1] = 0 as u8; one[2] = 0 as u8; one[3] = 0 as u8 125 sys_setsockopt(fd, LHD_SOL_SOCKET, LHD_SO_REUSEADDR, one, 4) 126 let addr: *u8 = sys_mmap(16) 127 lhd_sockaddr(addr, LHD_PORT) 128 if sys_bind(fd, addr, 16) < 0 { return 11 } 129 if sys_listen(fd, LHD_BACKLOG) < 0 { return 12 } 130 nx_lib_index_build() // build the persisted inverted index once at startup (O(1) /api/search) 131 let banner: *u8 = "nx_lib_httpd: sovereign library API on 0.0.0.0:8095 (reads nx_lib_store; no python/sqlite/3rd-party)\n" as *u8 132 sys_write(1, banner, ls_strlen(banner)) 133 var served: i64 = 0 134 while served < LHD_BUDGET { 135 let cfd: i64 = sys_accept(fd) 136 if cfd >= 0 { lhd_handle(cfd); sys_close(cfd) } 137 served = served + 1 138 } 139 sys_close(fd) 140 return 0 141}