code wiki / (root) / nx_pages_daemon.nx

nx_pages_daemon.nx source

↩ module page · 204 lines · 8112 B

1// nx_pages_daemon.nx -- D1.2 of NISHI_DEPLOY_ROADMAP. Composes 2// nx_http_server (socket+accept) + http_parse (full request parse) + 3// nx_pages_vhost (Host-header routing) + nx_pages_static (file serving) 4// into a single accept-loop daemon. HTTP-only in this stone; 5// TLS/HTTPS lands in D1.3. 6// 7// Build (x86_64 native Linux ELF, static): 8// cd nishi-core/nxc2 9// ./nxc2.exe --target x86_64 runtime/nx_pages_daemon.nx > /tmp/d.s 10// wsl bash -c "as /tmp/d.s -o /tmp/d.o && ld /tmp/d.o -o nx_pages_daemon" 11// 12// Run: 13// ./nx_pages_daemon # listens on :8080 (config hardcoded V1) 14// 15// V1 limitations (each named so they can become D1.x sub-stones): 16// - port + vhost map are hardcoded; D1.6 adds config file 17// - single-threaded accept loop; D1.10 adds worker pool 18// - no keepalive; D1.x adds HTTP/1.1 persistent connections 19// - no TLS; D1.3 20// - no SIGHUP reload; D1.6 21// - no ACME loop; D1.5 22 23import "nx_syscalls.nx" 24import "nx_http.nx" 25import "nx_http_server.nx" 26import "nx_pages_vhost.nx" 27import "nx_pages_static.nx" 28import "nx_pages_config.nx" 29const NX_MAGIC_2048: i64 = 2048 30const NX_MAGIC_4096: i64 = 4096 31 32const NX_DAEMON_PORT: i64 = 18080 33const NX_DAEMON_BACKLOG: i64 = 128 34const NX_DAEMON_REQ_CAP: i64 = 16384 35const NX_DAEMON_RESP_CAP: i64 = 4194304 // 4 MB max response (covers WASM) 36const NX_DAEMON_HTTP_METHOD_GET: i64 = 1 37 38// ===== Static-config vhost setup (V1 hardcoded) ===================== 39 40// V1 hardcoded fallback used only if the config file can't be loaded 41// from /etc/nx_pages.conf. D1.6.2 wires nx_pages_config; D1.6.3 will 42// add SIGHUP reload. 43func _daemon_setup_vhosts_hardcoded(t: *NxVHostTable, cursor: *i64) -> i64 { 44 nx_vhost_add(t, cursor, 45 "nishifamily.com" as *u8, 46 "/tmp/nx-test-dist" as *u8) 47 nx_vhost_add(t, cursor, 48 "www.nishifamily.com" as *u8, 49 "/tmp/nx-test-dist" as *u8) 50 nx_vhost_add(t, cursor, 51 "andelinwest.com" as *u8, 52 "/share/andelin-pages/dist" as *u8) 53 nx_vhost_set_fallback(t, 0 as i64) 54 return 0 55} 56 57// Search a list of config paths in order; first successful load wins. 58// Banner reports which path was used so operators can verify the deploy 59// picked up the right config. Order: production /etc -> NAS layout -> 60// dev /tmp -> hardcoded fallback. 61func _daemon_setup_vhosts(t: *NxVHostTable, cursor: *i64) -> i64 { 62 let p1: *u8 = "/etc/nx_pages.conf" as *u8 63 let p2: *u8 = "/volume1/docker/nishi-engine/nx_pages_deploy/nx_pages.conf" as *u8 64 let p3: *u8 = "/tmp/nx_pages.conf" as *u8 65 66 if nx_pages_config_load(p1, t, cursor) == NX_PCFG_OK { 67 let b: *u8 = "nx_pages_daemon: vhosts from /etc/nx_pages.conf\n" as *u8 68 var bn: i64 = 0 69 while b[bn] != 0 { bn = bn + 1 } 70 sys_write(1 as i64, b, bn) 71 return 0 72 } 73 if nx_pages_config_load(p2, t, cursor) == NX_PCFG_OK { 74 let b: *u8 = "nx_pages_daemon: vhosts from /volume1/docker/nishi-engine/nx_pages_deploy/nx_pages.conf\n" as *u8 75 var bn: i64 = 0 76 while b[bn] != 0 { bn = bn + 1 } 77 sys_write(1 as i64, b, bn) 78 return 0 79 } 80 if nx_pages_config_load(p3, t, cursor) == NX_PCFG_OK { 81 let b: *u8 = "nx_pages_daemon: vhosts from /tmp/nx_pages.conf\n" as *u8 82 var bn: i64 = 0 83 while b[bn] != 0 { bn = bn + 1 } 84 sys_write(1 as i64, b, bn) 85 return 0 86 } 87 let fb: *u8 = "nx_pages_daemon: no conf found; using hardcoded fallback\n" as *u8 88 var fbn: i64 = 0 89 while fb[fbn] != 0 { fbn = fbn + 1 } 90 sys_write(1 as i64, fb, fbn) 91 return _daemon_setup_vhosts_hardcoded(t, cursor) 92} 93 94// ===== Per-request handler ========================================== 95// 96// Reads + parses + dispatches one request on `cfd`. Always closes cfd. 97// Returns NXS_OK on full success, or one of the NXS_* error codes 98// (purely for daemon logging; closing happens unconditionally). 99 100func _daemon_serve_one(cfd: i64, table: *NxVHostTable) -> i64 { 101 let req_buf: *u8 = sys_mmap(NX_DAEMON_REQ_CAP) 102 let n: i64 = sys_read(cfd, req_buf, NX_DAEMON_REQ_CAP) 103 if n <= 0 { 104 sys_close(cfd) 105 return NXS_PARSE_ERR 106 } 107 108 let req: *HttpRequest = sys_mmap(NX_MAGIC_2048) as *HttpRequest 109 let parse_rc: i64 = http_parse(req_buf, n, req) 110 if parse_rc != 0 { 111 // Malformed -- send 400-ish via 405 emit then close (V1 simplification). 112 let scratch_buf: *u8 = sys_mmap(512) 113 let scratch_off: *i64 = sys_mmap(8) as *i64 114 scratch_off[0] = 0 115 nx_pages_emit_405(scratch_buf, scratch_off, 512 as i64) 116 nx_http_server_send_response(cfd, scratch_buf, scratch_off[0]) 117 return NXS_PARSE_ERR 118 } 119 120 // Vhost-dispatch on Host header. 121 let root_off: *i64 = sys_mmap(8) as *i64 122 let root_len: *i64 = sys_mmap(8) as *i64 123 let v: i64 = nx_vhost_dispatch(req_buf, req, table, root_off, root_len) 124 if v == NX_VHOST_NO_MATCH { 125 let nf_buf: *u8 = sys_mmap(512) 126 let nf_off: *i64 = sys_mmap(8) as *i64 127 nf_off[0] = 0 128 nx_pages_emit_404(nf_buf, nf_off, 512 as i64) 129 nx_http_server_send_response(cfd, nf_buf, nf_off[0]) 130 return NXS_OK 131 } 132 if v == NX_VHOST_NO_HOST_HDR { 133 let nh_buf: *u8 = sys_mmap(512) 134 let nh_off: *i64 = sys_mmap(8) as *i64 135 nh_off[0] = 0 136 nx_pages_emit_405(nh_buf, nh_off, 512 as i64) 137 nx_http_server_send_response(cfd, nh_buf, nh_off[0]) 138 return NXS_OK 139 } 140 141 // Map req.method bytes to method_kind (GET=1, others=0/unsupported). 142 var method_kind: i64 = 0 143 if req.method_len == 3 { 144 if req_buf[req.method_off] == 0x47 { // G 145 if req_buf[req.method_off + 1] == 0x45 { // E 146 if req_buf[req.method_off + 2] == 0x54 { // T 147 method_kind = NX_DAEMON_HTTP_METHOD_GET 148 } 149 } 150 } 151 } 152 153 // Serve the file via the vhost's doc root. 154 let resp_buf: *u8 = sys_mmap(NX_DAEMON_RESP_CAP) 155 let resp_n: *i64 = sys_mmap(8) as *i64 156 resp_n[0] = 0 157 let path: *u8 = ((req_buf as i64) + req.path_off) as *u8 158 let root: *u8 = ((table.arena as i64) + root_off[0]) as *u8 159 nx_pages_serve_file(path, req.path_len, 160 root, root_len[0], 161 method_kind, 162 resp_buf, NX_DAEMON_RESP_CAP, resp_n) 163 nx_http_server_send_response(cfd, resp_buf, resp_n[0]) 164 return NXS_OK 165} 166 167// ===== Main accept loop ============================================= 168 169func main() -> i64 { 170 // Build vhost table (V1 hardcoded). 171 let arena: *u8 = sys_mmap(NX_MAGIC_4096) 172 let vhosts: *i64 = sys_mmap(16 * NX_VHOST_STRIDE * 8) as *i64 173 let table: *NxVHostTable = sys_mmap(40) as *NxVHostTable 174 let cursor: *i64 = sys_mmap(8) as *i64 175 cursor[0] = 0 176 nx_vhost_table_init(table, arena, NX_MAGIC_4096 as i64, vhosts) 177 _daemon_setup_vhosts(table, cursor) 178 179 // Listen on :8080 (V1; D1.4 adds :80 + :443). 180 let addr_buf: *u8 = sys_mmap(16) 181 nx_http_server_addr_any(addr_buf, NX_DAEMON_PORT) 182 let verdict: *i64 = sys_mmap(8) as *i64 183 let listen_fd: i64 = nx_http_server_listen(addr_buf, NX_DAEMON_BACKLOG, verdict) 184 if listen_fd < 0 { 185 // Exit 30..39 = NXS_* verdict + 30 (gives operator the errno-ish reason). 186 // NXS_SOCKET_ERR=1 BIND_ERR=2 LISTEN_ERR=3 ACCEPT_ERR=4 ... 187 return 30 + verdict[0] 188 } 189 if listen_fd == 0 { return 21 } // exit 21 == listen returned 0 (unexpected) 190 // Diagnostic: dump verdict + fd to stdout for first-run visibility. 191 let banner: *u8 = "nx_pages_daemon: listen_fd > 0; entering accept loop\n" as *u8 192 var banner_n: i64 = 0 193 while banner[banner_n] != 0 { banner_n = banner_n + 1 } 194 sys_write(1 as i64, banner, banner_n) 195 196 // Accept loop. V1 = single-threaded, close after each response. 197 var running: i64 = 1 198 while running == 1 { 199 let cfd: i64 = nx_http_server_accept_one(listen_fd, verdict) 200 if cfd >= 0 { _daemon_serve_one(cfd, table) } 201 } 202 sys_close(listen_fd) 203 return 0 204}