code wiki / _hdl_build / nx_aw_devserver.nx
nx_aw_devserver.nx source
↩ module page · 208 lines · 12213 B
1// nx_aw_devserver.nx -- a REAL front-end for the CMS capabilities: a plain-HTTP server (loopback) that
2// ROUTES requests and renders responses LIVE using the actual capability organs -- so the features are
3// finally testable from a browser, not just unit gates. Routes:
4// GET / -> home, rendered from the i18n content store (English)
5// GET /?lang=fr -> SAME page in French (nx_cms_i18n RFC-4647 resolve, live)
6// GET /moderate?text=.. -> live comment moderation (nx_cms_comments score + verdict)
7// GET /price?cents=&qty=&tax= -> live integer-exact money (nx_cms_ecommerce, no float)
8// GET /ab?visitor=.. -> live deterministic A/B variant assignment (nx_cms_abtest)
9// GET /health -> ok
10// Composes nx_http_server (serve) + the capability organs. Plain HTTP for local front-end testing; the
11// live site uses the TLS daemon. license_tier: ORIGINAL
12import "nx_syscalls.nx"
13import "nx_http_server.nx"
14import "nx_cms_i18n.nx"
15import "nx_cms_comments.nx"
16import "nx_cms_ecommerce.nx"
17import "nx_cms_abtest.nx"
18const DS_MAGIC_8192: i64 = 8192
19const DS_MAGIC_2048: i64 = 2048
20const DS_MAGIC_65536: i64 = 65536
21const DS_MAGIC_70000: i64 = 70000
22const DS_MAGIC_100000: i64 = 100000
23const DS_MAGIC_1999: i64 = 1999
24
25const DS_PORT: i64 = 8099
26
27func ds_strlen(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} return n }
28func ds_app(dst: *u8, o: i64, s: *u8) -> i64 { var p: i64=o; var k: i64=0; while s[k]!=(0 as u8){dst[p]=s[k];p=p+1;k=k+1} return p }
29func ds_appb(dst: *u8, o: i64, src: *u8, n: i64) -> i64 { var p: i64=o; var k: i64=0; while k<n{dst[p]=src[k];p=p+1;k=k+1} return p }
30func ds_appn(dst: *u8, o: i64, v: i64) -> i64 {
31 var p: i64=o; let t: *u8=sys_mmap(28); var m: i64=v; if m<0{m=0-m; dst[p]=45 as u8; p=p+1}; var k: i64=0
32 if m==0 {t[0]=48 as u8;k=1}; while m>0 {t[k]=(48+(m%10)) as u8; m=m/10; k=k+1}
33 var i: i64=0; while i<k {dst[p]=t[k-1-i]; p=p+1; i=i+1} return p
34}
35
36func ds_find(buf: *u8, n: i64, pat: *u8, plen: i64) -> i64 {
37 if plen==0 { return 0-1 }
38 var i: i64=0
39 while i+plen<=n {
40 var j: i64=0; var ok: i64=1
41 while j<plen { if (buf[i+j] as i64)!=(pat[j] as i64){ok=0;j=plen} else {j=j+1} }
42 if ok==1 { return i }
43 i=i+1
44 }
45 return 0-1
46}
47
48// extract the request target (path+query) from "GET <target> HTTP/.." into out; returns length
49func ds_target(req: *u8, rn: i64, out: *u8, cap: i64) -> i64 {
50 var i: i64=0
51 while i<rn { if (req[i] as i64)==32 { break } i=i+1 }
52 i=i+1
53 var o: i64=0
54 while i<rn { let c: i64=req[i] as i64; if c==32 { break } if o<cap-1 { out[o]=req[i]; o=o+1 } i=i+1 }
55 out[o]=0 as u8
56 return o
57}
58
59// does s (sl bytes) start with NUL-terminated pat?
60func ds_starts(s: *u8, sl: i64, pat: *u8) -> i64 {
61 var i: i64=0
62 while pat[i]!=(0 as u8) { if i>=sl { return 0 } if (s[i] as i64)!=(pat[i] as i64){return 0} i=i+1 }
63 return 1
64}
65
66// copy the value of query key (e.g. "text=") into out, '+' -> space, until '&'/end. -1 if absent.
67func ds_qstr(target: *u8, tl: i64, key: *u8, out: *u8, cap: i64) -> i64 {
68 let kl: i64=ds_strlen(key)
69 let at: i64=ds_find(target, tl, key, kl)
70 if at<0 { return 0-1 }
71 var i: i64=at+kl; var o: i64=0
72 while i<tl { let c: i64=target[i] as i64; if c==38 { break } if o<cap-1 { if c==43 { out[o]=32 as u8 } else { out[o]=c as u8 } o=o+1 } i=i+1 }
73 out[o]=0 as u8
74 return o
75}
76
77// parse an unsigned int from query key; -1 if absent, 0 if present-but-empty.
78func ds_qint(target: *u8, tl: i64, key: *u8) -> i64 {
79 let kl: i64=ds_strlen(key)
80 let at: i64=ds_find(target, tl, key, kl)
81 if at<0 { return 0-1 }
82 var i: i64=at+kl; var v: i64=0; var any: i64=0
83 while i<tl { let c: i64=target[i] as i64; if c<48 { break } if c>57 { break } v=v*10+(c-48); any=any+1; i=i+1 }
84 if any==0 { return 0 }
85 return v
86}
87
88// shared page chrome (S-class-ish, single-quoted attrs so no quotes inside Nishi strings)
89func ds_head(body: *u8, o: i64, title: *u8) -> i64 {
90 var p: i64=o
91 p=ds_app(body, p, "<!doctype html><html><head><meta charset='utf-8'><meta name='viewport' content='width=device-width,initial-scale=1'><title>" as *u8)
92 p=ds_app(body, p, title)
93 p=ds_app(body, p, "</title><style>body{font:16px/1.6 system-ui,sans-serif;max-width:760px;margin:40px auto;padding:0 16px;color:#1a1a2e}a{color:#3a6ea5}nav a{margin-right:14px}.card{background:#f6f7fb;border:1px solid #e3e6ef;border-radius:10px;padding:16px 20px;margin:14px 0}.tag{display:inline-block;padding:2px 8px;border-radius:6px;font-size:13px}.ok{background:#dff5e1;color:#1b6b32}.bad{background:#fde0e0;color:#a11}.muted{color:#667}</style></head><body>" as *u8)
94 p=ds_app(body, p, "<nav><a href='/'>Home</a><a href='/?lang=fr'>FR</a><a href='/moderate?text=buy+casino+loan+http://x'>Moderation</a><a href='/price?cents=1999&qty=3&tax=825'>Pricing</a><a href='/ab?visitor=you'>A/B</a></nav><hr>" as *u8)
95 return p
96}
97func ds_foot(body: *u8, o: i64) -> i64 {
98 return ds_app(body, o, "<hr><p class='muted'>Served live by nx_aw_devserver -- each route renders through a real Nishi CMS capability organ (no static HTML).</p></body></html>" as *u8)
99}
100
101func main() -> i64 {
102 // i18n content store (locale-tagged), built once
103 let store: *u8 = sys_mmap(DS_MAGIC_8192)
104 let sn: i64 = ds_app(store, 0, "@title.en\nAndelin West, PLLC\n@title.fr\nAndelin West, SARL\n@intro.en\nSovereign legal services, served from the silicon up.\n@intro.fr\nServices juridiques souverains, du silicium vers le haut.\n@title\nAndelin West\n@intro\nLegal services.\n" as *u8)
105
106 let addr: *u8 = sys_mmap(16)
107 nx_http_server_addr_any(addr, DS_PORT) // 0.0.0.0 -- LAN-reachable so the same binary serves as DEV on the NAS
108 let lv: *i64 = sys_mmap(8) as *i64
109 let lfd: i64 = nx_http_server_listen(addr, 16, lv)
110 if lfd < 0 { sys_write(1, "DEVSERVER listen-fail\n" as *u8, 22); return 4 }
111 sys_write(1, "nx_aw_devserver listening 0.0.0.0:8088 (dev)\n" as *u8, 45)
112
113 // hoisted per-connection buffers
114 let av: *i64 = sys_mmap(8) as *i64
115 let req: *u8 = sys_mmap(DS_MAGIC_8192)
116 let tgt: *u8 = sys_mmap(DS_MAGIC_2048)
117 let arg: *u8 = sys_mmap(DS_MAGIC_2048)
118 let body: *u8 = sys_mmap(DS_MAGIC_65536)
119 let resp: *u8 = sys_mmap(DS_MAGIC_70000)
120 let vbuf: *u8 = sys_mmap(512)
121
122 var served: i64 = 0
123 while served < DS_MAGIC_100000 {
124 let cfd: i64 = nx_http_server_accept_one(lfd, av)
125 if cfd < 0 { served = served + 1; continue }
126 let rn: i64 = sys_read(cfd, req, DS_MAGIC_8192)
127 var tl: i64 = 0
128 if rn > 0 { tl = ds_target(req, rn, tgt, DS_MAGIC_2048) }
129
130 var bo: i64 = 0
131
132 // ---- routing ----
133 if ds_starts(tgt, tl, "/health" as *u8) == 1 {
134 bo = ds_app(body, bo, "ok" as *u8)
135 }
136 if ds_starts(tgt, tl, "/moderate" as *u8) == 1 {
137 let al: i64 = ds_qstr(tgt, tl, "text=" as *u8, arg, DS_MAGIC_2048)
138 bo = ds_head(body, bo, "Live comment moderation" as *u8)
139 bo = ds_app(body, bo, "<h1>Comment moderation (live)</h1><p class='muted'>Edit <code>?text=</code> in the URL and reload.</p><div class='card'><b>Comment:</b> " as *u8)
140 if al >= 0 { bo = ds_appb(body, bo, arg, al) } else { bo = ds_app(body, bo, "<i>(none)</i>" as *u8) }
141 var sc: i64 = 0
142 if al > 0 { sc = cmt_score(arg, al) }
143 let verdict: i64 = cmt_classify(sc)
144 bo = ds_app(body, bo, "<p>spam score = <b>" as *u8); bo = ds_appn(body, bo, sc)
145 bo = ds_app(body, bo, "</b> (threshold " as *u8); bo = ds_appn(body, bo, NX_CMT_SPAM_THRESHOLD)
146 bo = ds_app(body, bo, ")</p><p>verdict: " as *u8)
147 if verdict == NX_CMT_SPAM { bo = ds_app(body, bo, "<span class='tag bad'>SPAM - withheld from the public page</span>" as *u8) }
148 else { bo = ds_app(body, bo, "<span class='tag ok'>PENDING - queued for moderation (never auto-published)</span>" as *u8) }
149 bo = ds_app(body, bo, "</div>" as *u8)
150 bo = ds_foot(body, bo)
151 }
152 if ds_starts(tgt, tl, "/price" as *u8) == 1 {
153 var cents: i64 = ds_qint(tgt, tl, "cents="); if cents < 0 { cents = DS_MAGIC_1999 }
154 var qty: i64 = ds_qint(tgt, tl, "qty="); if qty < 0 { qty = 1 }
155 var tax: i64 = ds_qint(tgt, tl, "tax="); if tax < 0 { tax = 0 }
156 let line: i64 = ec_line_total(cents, qty)
157 let total: i64 = ec_apply_tax(line, tax)
158 bo = ds_head(body, bo, "Live exact pricing" as *u8)
159 bo = ds_app(body, bo, "<h1>Integer-exact pricing (live)</h1><p class='muted'>Edit <code>?cents=&qty=&tax=</code> (tax in basis points; 825 = 8.25%).</p><div class='card'>" as *u8)
160 bo = ds_app(body, bo, "<p>unit: $" as *u8); bo = ds_appn(body, bo, cents/100); bo = ds_app(body, bo, "." as *u8); if cents%100 < 10 { bo = ds_app(body, bo, "0" as *u8) } bo = ds_appn(body, bo, cents%100)
161 bo = ds_app(body, bo, " × " as *u8); bo = ds_appn(body, bo, qty)
162 bo = ds_app(body, bo, "</p><p>line total: $" as *u8); bo = ds_appn(body, bo, line/100); bo = ds_app(body, bo, "." as *u8); if line%100 < 10 { bo = ds_app(body, bo, "0" as *u8) } bo = ds_appn(body, bo, line%100)
163 bo = ds_app(body, bo, "</p><p><b>total with tax: $" as *u8); bo = ds_appn(body, bo, total/100); bo = ds_app(body, bo, "." as *u8); if total%100 < 10 { bo = ds_app(body, bo, "0" as *u8) } bo = ds_appn(body, bo, total%100)
164 bo = ds_app(body, bo, "</b> = " as *u8); bo = ds_appn(body, bo, total); bo = ds_app(body, bo, " cents (exact, no float)</p></div>" as *u8)
165 bo = ds_foot(body, bo)
166 }
167 if ds_starts(tgt, tl, "/ab" as *u8) == 1 {
168 let al: i64 = ds_qstr(tgt, tl, "visitor=" as *u8, arg, DS_MAGIC_2048)
169 var vp: *u8 = "anon" as *u8
170 if al > 0 { vp = arg }
171 let variant: i64 = ab_assign("homepage-cta" as *u8, vp, 2)
172 bo = ds_head(body, bo, "Live A/B assignment" as *u8)
173 bo = ds_app(body, bo, "<h1>A/B variant (live, deterministic)</h1><p class='muted'>Edit <code>?visitor=</code> -- the same visitor always gets the same arm, with no stored state.</p><div class='card'><p>visitor: <b>" as *u8)
174 bo = ds_appb(body, bo, vp, ds_strlen(vp))
175 bo = ds_app(body, bo, "</b></p><p>assigned arm: " as *u8)
176 if variant == 0 { bo = ds_app(body, bo, "<span class='tag ok'>A — Get a free consultation</span>" as *u8) }
177 else { bo = ds_app(body, bo, "<span class='tag ok'>B — Talk to an attorney today</span>" as *u8) }
178 bo = ds_app(body, bo, "</p></div>" as *u8)
179 bo = ds_foot(body, bo)
180 }
181 // home (default): i18n-resolved title + intro
182 if bo == 0 {
183 var locale: *u8 = "en" as *u8
184 if ds_find(tgt, tl, "lang=fr" as *u8, 7) >= 0 { locale = "fr" as *u8 }
185 let tier: *i64 = sys_mmap(16) as *i64
186 let tv: i64 = i18n_resolve(store, sn, "title" as *u8, locale, "en" as *u8, vbuf, 256, tier)
187 bo = ds_head(body, bo, "Andelin West" as *u8)
188 bo = ds_app(body, bo, "<h1>" as *u8); if tv >= 0 { bo = ds_appb(body, bo, vbuf, tv) }
189 bo = ds_app(body, bo, "</h1><p>" as *u8)
190 let iv: i64 = i18n_resolve(store, sn, "intro" as *u8, locale, "en" as *u8, vbuf, 256, tier)
191 if iv >= 0 { bo = ds_appb(body, bo, vbuf, iv) }
192 bo = ds_app(body, bo, "</p><p class='muted'>locale served: " as *u8); bo = ds_appb(body, bo, locale, 2)
193 bo = ds_app(body, bo, " — try <a href='/?lang=fr'>?lang=fr</a>. Every nav link above is a live capability.</p>" as *u8)
194 bo = ds_foot(body, bo)
195 }
196
197 // ---- response ----
198 var ro: i64 = ds_app(resp, 0, "HTTP/1.0 200 OK\r\nContent-Type: text/html; charset=utf-8\r\nContent-Length: " as *u8)
199 ro = ds_appn(resp, ro, bo)
200 ro = ds_app(resp, ro, "\r\nConnection: close\r\nX-Served-By: nx_aw_devserver\r\n\r\n" as *u8)
201 ro = ds_appb(resp, ro, body, bo)
202 sys_write(cfd, resp, ro)
203 sys_close(cfd)
204 served = served + 1
205 }
206 sys_close(lfd)
207 return 0
208}