code wiki / (root) / nx_search_crawl_server.nx

nx_search_crawl_server.nx source

↩ module page · 224 lines · 10015 B

1// nx_search_crawl_server.nx -- LIVE search site over CRAWLED content. 2// 3// module: nishi-core.search.crawl_server 4// depends: nx_http_server, nx_http_client, nx_html_to_text, nx_simhash, 5// nx_rank_fused, fx, nx_str 6// capability: APP_RUNNABLE (DAEMON) 7// 8// The integrated end-state: at startup the daemon CRAWLS a seed list over our 9// own HTTP client (fetch -> extract text -> content-dedup -> index), then 10// serves a live search site that ranks the SCRAPED corpus per query 11// (nx_rank_fused: tier + BM25 content via RRF). Everything is surfaced by 12// relevance -- no category censorship, no provenance weighting (operator 13// decision 2026-05-29: maximal coverage). Demonstrated on loopback against 14// nx_mock_web; in the networked env the seeds are real URLs over DNS/TLS/HTTP. 15 16import "fx.nx" 17import "nx_str.nx" 18import "nx_http_server.nx" 19import "nx_http_client.nx" 20import "nx_html_to_text.nx" 21import "nx_simhash.nx" 22import "nx_rank_fused.nx" 23 24const NX_SCS_PORT: i64 = 8790 25const NX_SCS_CRAWL_PORT: i64 = 8088 // the mock-web (or any seed host) 26 27func bputc(buf: *u8, pos: *i64, cap: i64, c: i64) -> i64 { if pos[0] < cap { buf[pos[0]] = c; pos[0] = pos[0] + 1 } return 0 } 28func bput(buf: *u8, pos: *i64, cap: i64, s: *u8) -> i64 { let n: i64 = nx_str_len(s); var i: i64 = 0; while i < n { bputc(buf, pos, cap, s[i] as i64); i = i + 1 } return 0 } 29 30func nx_body_off(resp: *u8, n: i64) -> i64 { 31 var i: i64 = 0 32 while i < n - 3 { 33 if (resp[i] as i64) == 0x0D { if (resp[i+1] as i64) == 0x0A { if (resp[i+2] as i64) == 0x0D { if (resp[i+3] as i64) == 0x0A { return i + 4 } } } } 34 i = i + 1 35 } 36 return 0 37} 38 39// extract <title>..</title> text into out; returns len (0 if none). 40func nx_extract_title(html: *u8, n: i64, out: *u8, outcap: i64) -> i64 { 41 let tag: *u8 = "<title>" 42 let tl: i64 = 7 43 var i: i64 = 0 44 var ts: i64 = 0 - 1 45 while i <= n - tl { 46 if ts < 0 { 47 var j: i64 = 0 48 var ok: i64 = 1 49 while j < tl { if (html[i + j] as i64) != (tag[j] as i64) { ok = 0; j = tl } else { j = j + 1 } } 50 if ok == 1 { ts = i + tl } 51 } 52 i = i + 1 53 } 54 if ts < 0 { return 0 } 55 var olen: i64 = 0 56 var p: i64 = ts 57 var run: i64 = 1 58 while run == 1 { 59 run = 0 60 if p < n { if (html[p] as i64) != 0x3C { if olen < outcap { out[olen] = html[p]; olen = olen + 1 } p = p + 1; run = 1 } } 61 } 62 return olen 63} 64 65func nx_extract_query(req: *u8, path_off: i64, path_len: i64, out: *u8, out_cap: i64) -> i64 { 66 var i: i64 = path_off 67 let end: i64 = path_off + path_len 68 var qstart: i64 = 0 - 1 69 while i < end - 1 { 70 if (req[i] as i64) == 0x71 { if (req[i + 1] as i64) == 0x3D { qstart = i + 2; i = end } } 71 if qstart < 0 { i = i + 1 } 72 } 73 if qstart < 0 { return 0 } 74 var p: i64 = qstart 75 var olen: i64 = 0 76 var run: i64 = 1 77 while run == 1 { 78 run = 0 79 if p < end { 80 let c: i64 = req[p] as i64 81 if c == 0x26 { run = 0 } else { 82 var emit: i64 = c 83 if c == 0x2B { emit = 0x20 } 84 if olen < out_cap { out[olen] = emit; olen = olen + 1 } 85 p = p + 1; run = 1 86 } 87 } 88 } 89 return olen 90} 91 92func nx_split_terms(qbuf: *u8, qlen: i64, tp: **u8, tl: *i64, max: i64) -> i64 { 93 var n: i64 = 0 94 var i: i64 = 0 95 while i < qlen { 96 if (qbuf[i] as i64) == 0x20 { i = i + 1 } else { 97 let start: i64 = i 98 var run: i64 = 1 99 while run == 1 { run = 0; if i < qlen { if (qbuf[i] as i64) != 0x20 { i = i + 1; run = 1 } } } 100 if n < max { tp[n] = ((qbuf as i64) + start) as *u8; tl[n] = i - start; n = n + 1 } 101 } 102 i = i + 1 103 } 104 return n 105} 106 107func main() -> i64 { 108 // ---- STARTUP CRAWL: fetch seeds over our HTTP client, extract, dedup, index ---- 109 let caddr: *u8 = sys_mmap(16) 110 nx_http_client_sockaddr_ipv4(caddr, 127, 0, 0, 1, NX_SCS_CRAWL_PORT) 111 let host: *u8 = "127.0.0.1" 112 let hlen: i64 = nx_str_len(host) 113 114 let NSEED: i64 = 3 115 let seeds: **u8 = sys_mmap(NSEED * 8) as **u8 116 seeds[0] = "/wiki"; seeds[1] = "/thothub"; seeds[2] = "/fan" 117 118 let curl: **u8 = sys_mmap(64 * 8) as **u8 119 let ctit: **u8 = sys_mmap(64 * 8) as **u8 120 let ctext: **u8 = sys_mmap(64 * 8) as **u8 121 let ctlen: *i64 = sys_mmap(64 * 8) as *i64 122 let keptfp: *i64 = sys_mmap(64 * 8) as *i64 123 var ndoc: i64 = 0 124 125 var s: i64 = 0 126 while s < NSEED { 127 let resp: *u8 = sys_mmap(16384) 128 let vb: *i64 = sys_mmap(8) as *i64 129 let n: i64 = nx_http_client_get(caddr, seeds[s], nx_str_len(seeds[s]), host, hlen, resp, 16384, vb) 130 if n > 0 { 131 let bo: i64 = nx_body_off(resp, n) 132 let text: *u8 = sys_mmap(8192) 133 let tlen: i64 = nx_html_to_text(((resp as i64) + bo) as *u8, n - bo, text, 8192) 134 let fp: i64 = nx_simhash_fingerprint(text, tlen) 135 var dup: i64 = 0 136 var k: i64 = 0 137 while k < ndoc { if nx_simhash_hamming(fp, keptfp[k]) <= 6 { dup = 1 } k = k + 1 } 138 if dup == 0 { 139 let title: *u8 = sys_mmap(256) 140 let tnl: i64 = nx_extract_title(((resp as i64) + bo) as *u8, n - bo, title, 255) 141 title[tnl] = 0 142 // full url for display: host + path 143 let fu: *u8 = sys_mmap(256) 144 let fp2: *i64 = sys_mmap(8) as *i64 145 fp2[0] = 0 146 bput(fu, fp2, 256, "http://127.0.0.1:8088") 147 bput(fu, fp2, 256, seeds[s]) 148 fu[fp2[0]] = 0 149 curl[ndoc] = fu; ctit[ndoc] = title; ctext[ndoc] = text; ctlen[ndoc] = tlen; keptfp[ndoc] = fp 150 ndoc = ndoc + 1 151 } 152 } 153 s = s + 1 154 } 155 156 // ---- SERVE ---- 157 let addr: *u8 = sys_mmap(16) 158 nx_http_server_addr_any(addr, NX_SCS_PORT) 159 let vbox: *i64 = sys_mmap(8) as *i64 160 let lfd: i64 = nx_http_server_listen(addr, 16, vbox) 161 if lfd < 0 { return 2 } 162 163 var serving: i64 = 1 164 while serving == 1 { 165 let cfd: i64 = nx_http_server_accept_one(lfd, vbox) 166 if cfd >= 0 { 167 let req: *u8 = sys_mmap(8192) 168 let m: *i64 = sys_mmap(8) as *i64 169 let po: *i64 = sys_mmap(8) as *i64 170 let pl: *i64 = sys_mmap(8) as *i64 171 let cl: *i64 = sys_mmap(8) as *i64 172 let bo2: *i64 = sys_mmap(8) as *i64 173 let rn: *i64 = sys_mmap(8) as *i64 174 let rc: i64 = nx_http_server_read_request(cfd, req, 8192, m, po, pl, cl, bo2, rn) 175 176 let resp: *u8 = sys_mmap(131072) 177 let pos: *i64 = sys_mmap(8) as *i64 178 pos[0] = 0 179 bput(resp, pos, 131072, "HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=utf-8\r\nConnection: close\r\n\r\n") 180 bput(resp, pos, 131072, "<!doctype html><html><head><meta charset='utf-8'><title>Nishi Search (crawled)</title><style>body{font-family:system-ui;background:#0b0e14;color:#dde3ee;max-width:820px;margin:0 auto;padding:28px}h1{font-size:19px}form{display:flex;gap:8px;margin:14px 0}input{flex:1;padding:10px 13px;border-radius:9px;border:1px solid #25304a;background:#0f1422;color:#fff}button{padding:10px 16px;border:0;border-radius:9px;background:#2a6df4;color:#fff;font-weight:600}ol{list-style:none;padding:0;counter-reset:r}li{counter-increment:r;padding:9px 0;border-bottom:1px solid #161d29}li::before{content:counter(r) '. ';color:#5b6b86}.t{color:#eef2f8}.u{color:#5b6b86;font-size:12px}.sub{color:#8ea0c0;font-size:13px}</style></head><body>") 181 bput(resp, pos, 131072, "<h1>NISHI SEARCH &mdash; over our own crawl</h1><p class='sub'>corpus: ") 182 bput(resp, pos, 131072, "") 183 // crawled-count 184 let cnt: *u8 = sys_mmap(16); let cp: *i64 = sys_mmap(8) as *i64; cp[0] = 0; bputc(cnt, cp, 16, 0x30 + ndoc); cnt[cp[0]] = 0 185 bput(resp, pos, 131072, cnt) 186 bput(resp, pos, 131072, " pages scraped from the loopback web (live crawl in the networked env)</p>") 187 bput(resp, pos, 131072, "<form action='/search'><input name='q' placeholder='Search the crawl&hellip;' value='") 188 189 let qbuf: *u8 = sys_mmap(1024) 190 var qlen: i64 = 0 191 if rc == NXS_OK { qlen = nx_extract_query(req, po[0], pl[0], qbuf, 1024) } 192 var qe: i64 = 0 193 while qe < qlen { bputc(resp, pos, 131072, qbuf[qe] as i64); qe = qe + 1 } 194 bput(resp, pos, 131072, "' autofocus><button>Search</button></form>") 195 196 if qlen > 0 { 197 let tp: **u8 = sys_mmap(8 * 8) as **u8 198 let tl: *i64 = sys_mmap(8 * 8) as *i64 199 let nterms: i64 = nx_split_terms(qbuf, qlen, tp, tl, 8) 200 let order: *i64 = sys_mmap(64 * 8) as *i64 201 nx_rank_fused(curl, ctit, ctext, ctlen, ndoc, tp, tl, nterms, order) 202 bput(resp, pos, 131072, "<ol>") 203 var shown: i64 = 0 204 var i: i64 = 0 205 while i < ndoc { 206 let d: i64 = order[i] 207 var matched: i64 = 0 208 var t: i64 = 0 209 while t < nterms { if nx_bm25_tf(ctext[d], ctlen[d], nx_inv_hash_bytes_lower(tp[t], tl[t])) > 0 { matched = 1 } t = t + 1 } 210 if matched == 1 { 211 bput(resp, pos, 131072, "<li><span class='t'>"); bput(resp, pos, 131072, ctit[d]); bput(resp, pos, 131072, "</span><div class='u'>"); bput(resp, pos, 131072, curl[d]); bput(resp, pos, 131072, "</div></li>") 212 shown = shown + 1 213 } 214 i = i + 1 215 } 216 if shown == 0 { bput(resp, pos, 131072, "<li class='sub'>no matches in the crawl</li>") } 217 bput(resp, pos, 131072, "</ol>") 218 } 219 bput(resp, pos, 131072, "</body></html>") 220 nx_http_server_send_response(cfd, resp, pos[0]) 221 } 222 } 223 return 0 224}