code wiki / (root) / nx_pages_http_redirect.nx

nx_pages_http_redirect.nx source

↩ module page · 153 lines · 5288 B

1// nx_pages_http_redirect.nx -- D1.4 of NISHI_DEPLOY_ROADMAP. 2// 3// Tiny daemon that listens on :80 and 301-redirects every request to 4// the same Host + path on https://. This is the partner of D1.3's 5// nx_pages_https_daemon -- together they replace nginx's TLS termination 6// + ACME http-01 challenge surface (when D1.5 lands, this daemon also 7// serves /.well-known/acme-challenge/ for renewals). 8// 9// V1 behaviour: 10// - GET / -> 301 Location: https://<Host>/ 11// - GET /foo/bar -> 301 Location: https://<Host>/foo/bar 12// - No Host header -> 301 Location: https://nishifamily.com/<path> 13// 14// V2 hook (D1.5): if path starts with /.well-known/acme-challenge/, 15// serve the matching token from a config-driven directory instead of 16// redirecting. Tracked as TODO in this file. 17 18import "nx_syscalls.nx" 19import "nx_http.nx" 20import "nx_http_server.nx" 21const NX_MAGIC_2048: i64 = 2048 22 23const NX_REDIRECT_PORT: i64 = 80 24const NX_REDIRECT_BACKLOG: i64 = 64 25const NX_REDIRECT_REQ_CAP: i64 = 8192 26const NX_REDIRECT_OUT_CAP: i64 = 8192 27 28const NX_REDIRECT_OK: i64 = 0 29const NX_REDIRECT_ERR_LISTEN: i64 = 1 30const NX_REDIRECT_ERR_PARSE: i64 = 2 31 32// Default host used when the request has no Host header. Production 33// SHOULD always have a Host header; this is the defensive default. 34// D1.6 will make it configurable. Returned by a function since 35// NishiLang const only accepts integer literals. 36func _redirect_default_host() -> *u8 { return "nishifamily.com" as *u8 } 37 38// Build a 301 redirect response into `out_buf`. Writes the response 39// length to *out_n. Format: 40// HTTP/1.1 301 Moved Permanently\r\n 41// Location: https://<host>/<path>\r\n 42// Content-Length: 0\r\n 43// Connection: close\r\n 44// \r\n 45// 46// host_buf+off..off+len = the Host bytes from the request (or default 47// host fallback bytes if not present). path_buf+path_off..+path_len 48// = the request path. 49func _build_301( 50 out_buf: *u8, out_cap: i64, 51 host_buf: *u8, host_off: i64, host_len: i64, 52 path_buf: *u8, path_off: i64, path_len: i64, 53 out_n: *i64 54) -> i64 { 55 var w: i64 = 0 56 let line1: *u8 = "HTTP/1.1 301 Moved Permanently\r\nLocation: https://" as *u8 57 var i: i64 = 0 58 while line1[i] != 0 { out_buf[w] = line1[i]; w = w + 1; i = i + 1 } 59 60 // Write Host bytes. 61 var hi: i64 = 0 62 while hi < host_len { 63 out_buf[w] = host_buf[host_off + hi] 64 w = w + 1 65 hi = hi + 1 66 } 67 68 // Write path bytes (already starts with / per HTTP/1.1 request-line). 69 var pi: i64 = 0 70 while pi < path_len { 71 out_buf[w] = path_buf[path_off + pi] 72 w = w + 1 73 pi = pi + 1 74 } 75 76 let line2: *u8 = "\r\nContent-Length: 0\r\nConnection: close\r\n\r\n" as *u8 77 var j: i64 = 0 78 while line2[j] != 0 { out_buf[w] = line2[j]; w = w + 1; j = j + 1 } 79 80 out_n[0] = w 81 return 0 82} 83 84// Per-connection: read request, parse, send 301, close. 85func _redirect_serve_one(cfd: i64) -> i64 { 86 let req_buf: *u8 = sys_mmap(NX_REDIRECT_REQ_CAP) 87 let n: i64 = sys_read(cfd, req_buf, NX_REDIRECT_REQ_CAP) 88 if n <= 0 { 89 sys_close(cfd) 90 return NX_REDIRECT_ERR_PARSE 91 } 92 93 let req: *HttpRequest = sys_mmap(NX_MAGIC_2048) as *HttpRequest 94 let parse_rc: i64 = http_parse(req_buf, n, req) 95 if parse_rc != 0 { 96 sys_close(cfd) 97 return NX_REDIRECT_ERR_PARSE 98 } 99 100 // Look up Host header value. 101 let host_off: *i64 = sys_mmap(8) as *i64 102 let host_len: *i64 = sys_mmap(8) as *i64 103 let has_host: i64 = http_get_header(req_buf, req, "Host" as *u8, 104 host_off, host_len) 105 106 let out_buf: *u8 = sys_mmap(NX_REDIRECT_OUT_CAP) 107 let out_n: *i64 = sys_mmap(8) as *i64 108 if has_host == 1 { 109 _build_301(out_buf, NX_REDIRECT_OUT_CAP, 110 req_buf, host_off[0], host_len[0], 111 req_buf, req.path_off, req.path_len, 112 out_n) 113 } 114 if has_host != 1 { 115 // Fallback: nishifamily.com as the Host. 116 let dh: *u8 = _redirect_default_host() 117 var dh_len: i64 = 0 118 while dh[dh_len] != 0 { dh_len = dh_len + 1 } 119 _build_301(out_buf, NX_REDIRECT_OUT_CAP, 120 dh, 0 as i64, dh_len, 121 req_buf, req.path_off, req.path_len, 122 out_n) 123 } 124 125 nx_http_server_send_response(cfd, out_buf, out_n[0]) 126 return NX_REDIRECT_OK 127} 128 129// Entry point: listen :80, accept loop. 130func nx_pages_http_redirect_daemon_run() -> i64 { 131 let addr_buf: *u8 = sys_mmap(16) 132 nx_http_server_addr_any(addr_buf, NX_REDIRECT_PORT) 133 let verdict: *i64 = sys_mmap(8) as *i64 134 let listen_fd: i64 = nx_http_server_listen(addr_buf, NX_REDIRECT_BACKLOG, verdict) 135 if listen_fd < 0 { return NX_REDIRECT_ERR_LISTEN } 136 137 let banner: *u8 = "nx_pages_http_redirect: listening on :80 (-> https://)\n" as *u8 138 var bn: i64 = 0 139 while banner[bn] != 0 { bn = bn + 1 } 140 sys_write(1 as i64, banner, bn) 141 142 var running: i64 = 1 143 while running == 1 { 144 let cfd: i64 = nx_http_server_accept_one(listen_fd, verdict) 145 if cfd >= 0 { _redirect_serve_one(cfd) } 146 } 147 sys_close(listen_fd) 148 return NX_REDIRECT_OK 149} 150 151func main() -> i64 { 152 return nx_pages_http_redirect_daemon_run() 153}