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}