code wiki / (root) / nx_http_resp.nx

nx_http_resp.nx source

↩ module page · 204 lines · 7294 B

1// http_resp.nx -- HTTP/1.1 response generator. 2// 3// Companion to http.nx (request parser). Assembles a valid HTTP 4// response into a caller-mmapped output buffer: status line, 5// headers, body. No internal allocation beyond tiny scratch. 6// 7// Response shape (RFC 7230 §3): 8// HTTP-version SP status-code SP reason-phrase CRLF 9// *header-field CRLF 10// CRLF 11// [ message-body ] 12// 13// Implementation notes: 14// - Content-Length automatically emitted from body_len. Chunked 15// transfer encoding is a future extension. 16// - Connection: close emitted by default (no keep-alive yet). 17// - Server: nishi emitted for sovereignty attribution. 18// - Date header NOT emitted -- caller can pre-compute via 19// timefmt.nx if clock sync matters for their use case. 20// 21// Invariants: 22// HR1 Output buffer bounds-checked at every append; overrun 23// returns a negative error rather than silent truncation. 24// HR2 No internal state; each response is built from scratch. 25// Thread-safe for the caller's own isolation concerns. 26// HR3 Status codes + reason phrases kept in a fixed table; no 27// arbitrary int -> string formatting on the response path. 28// HR4 Content-Length header reflects body_len exactly; no 29// mismatched framing that would confuse HTTP/1.1 clients. 30 31// nx_safety_envelope: 32// intended_use: AUTO_APPLIED -- primitive-specific tuning queued 33// sil_target: SIL1 34// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail] 35// verdict: NOT_YET_EVALUATED 36 37import "nx_syscalls.nx" 38 39const HRR_ERR_OVERFLOW: i64 = -1 40 41// Common HTTP status codes + reason phrases. Callers pass the 42// numeric status; we map to a phrase string. Missing codes 43// fall through to "Unknown Status" which is technically legal 44// HTTP (RFC 7230 §3.1.2: reason phrase is "essentially ignored 45// by the recipient"). 46 47func http_reason(code: i64) -> *u8 { 48 if code == 200 { return "OK" } 49 if code == 201 { return "Created" } 50 if code == 204 { return "No Content" } 51 if code == 301 { return "Moved Permanently" } 52 if code == 302 { return "Found" } 53 if code == 304 { return "Not Modified" } 54 if code == 400 { return "Bad Request" } 55 if code == 401 { return "Unauthorized" } 56 if code == 403 { return "Forbidden" } 57 if code == 404 { return "Not Found" } 58 if code == 405 { return "Method Not Allowed" } 59 if code == 413 { return "Payload Too Large" } 60 if code == 429 { return "Too Many Requests" } 61 if code == 500 { return "Internal Server Error" } 62 if code == 501 { return "Not Implemented" } 63 if code == 503 { return "Service Unavailable" } 64 return "Unknown Status" 65} 66 67// Append a null-terminated cstring to out at *pos. Bounds-check 68// against cap. Returns 0 on success, HRR_ERR_OVERFLOW on 69// overflow. 70func http_append_cstr(out: *u8, pos: *i64, cap: i64, s: *u8) -> i64 { 71 let p0: i64 = *pos 72 var i: i64 = 0 73 while s[i] != 0 { 74 if p0 + i >= cap { return HRR_ERR_OVERFLOW } 75 out[p0 + i] = s[i] 76 i = i + 1 77 } 78 *pos = p0 + i 79 return 0 80} 81 82// Append n bytes from src. 83func http_append_bytes(out: *u8, pos: *i64, cap: i64, 84 src: *u8, n: i64) -> i64 { 85 let p0: i64 = *pos 86 if p0 + n > cap { return HRR_ERR_OVERFLOW } 87 var i: i64 = 0 88 while i < n { out[p0 + i] = src[i]; i = i + 1 } 89 *pos = p0 + n 90 return 0 91} 92 93// Append a decimal-formatted i64 (non-negative). 94func http_append_dec(out: *u8, pos: *i64, cap: i64, n: i64) -> i64 { 95 let p0: i64 = *pos 96 if n == 0 { 97 if p0 >= cap { return HRR_ERR_OVERFLOW } 98 out[p0] = 0x30 99 *pos = p0 + 1 100 return 0 101 } 102 let scratch: *u8 = sys_mmap(32) 103 var v: i64 = n 104 var k: i64 = 0 105 while v > 0 { 106 scratch[k] = 0x30 + (v % 10) 107 v = v / 10 108 k = k + 1 109 } 110 if p0 + k > cap { return HRR_ERR_OVERFLOW } 111 var i: i64 = 0 112 while i < k { 113 out[p0 + i] = scratch[k - 1 - i] 114 i = i + 1 115 } 116 *pos = p0 + k 117 return 0 118} 119 120// Emit status line. "HTTP/1.1 <code> <reason>\r\n". 121func http_emit_status(out: *u8, pos: *i64, cap: i64, code: i64) -> i64 { 122 let rc0: i64 = http_append_cstr(out, pos, cap, "HTTP/1.1 ") 123 if rc0 < 0 { return rc0 } 124 let rc1: i64 = http_append_dec(out, pos, cap, code) 125 if rc1 < 0 { return rc1 } 126 let rc2: i64 = http_append_cstr(out, pos, cap, " ") 127 if rc2 < 0 { return rc2 } 128 let rc3: i64 = http_append_cstr(out, pos, cap, http_reason(code)) 129 if rc3 < 0 { return rc3 } 130 let rc4: i64 = http_append_cstr(out, pos, cap, "\r\n") 131 return rc4 132} 133 134// Emit "Name: value\r\n". `value` is a null-terminated cstring. 135func http_emit_header(out: *u8, pos: *i64, cap: i64, 136 name: *u8, value: *u8) -> i64 { 137 let rc0: i64 = http_append_cstr(out, pos, cap, name) 138 if rc0 < 0 { return rc0 } 139 let rc1: i64 = http_append_cstr(out, pos, cap, ": ") 140 if rc1 < 0 { return rc1 } 141 let rc2: i64 = http_append_cstr(out, pos, cap, value) 142 if rc2 < 0 { return rc2 } 143 return http_append_cstr(out, pos, cap, "\r\n") 144} 145 146// Emit "Content-Length: N\r\n" with numeric value. 147func http_emit_content_length(out: *u8, pos: *i64, cap: i64, 148 n: i64) -> i64 { 149 let rc0: i64 = http_append_cstr(out, pos, cap, "Content-Length: ") 150 if rc0 < 0 { return rc0 } 151 let rc1: i64 = http_append_dec(out, pos, cap, n) 152 if rc1 < 0 { return rc1 } 153 return http_append_cstr(out, pos, cap, "\r\n") 154} 155 156// Build a complete response: status + minimal headers + body. 157// Emitted headers: 158// Server: nishi 159// Connection: close 160// Content-Type: <content_type> (caller-supplied cstring) 161// Content-Length: <body_len> 162// Returns total bytes written or HRR_ERR_OVERFLOW. 163func http_build_response(out: *u8, cap: i64, 164 status_code: i64, 165 content_type: *u8, 166 body: *u8, body_len: i64) -> i64 { 167 let pos_raw: *u8 = sys_mmap(16) 168 let pos: *i64 = pos_raw as *i64 169 *pos = 0 170 171 let r1: i64 = http_emit_status(out, pos, cap, status_code) 172 if r1 < 0 { return r1 } 173 let r2: i64 = http_emit_header(out, pos, cap, "Server", "nishi") 174 if r2 < 0 { return r2 } 175 let r3: i64 = http_emit_header(out, pos, cap, "Connection", "close") 176 if r3 < 0 { return r3 } 177 let r4: i64 = http_emit_header(out, pos, cap, "Content-Type", 178 content_type) 179 if r4 < 0 { return r4 } 180 let r5: i64 = http_emit_content_length(out, pos, cap, body_len) 181 if r5 < 0 { return r5 } 182 // Blank line terminates headers. 183 let r6: i64 = http_append_cstr(out, pos, cap, "\r\n") 184 if r6 < 0 { return r6 } 185 // Body. 186 if body_len > 0 { 187 let r7: i64 = http_append_bytes(out, pos, cap, body, body_len) 188 if r7 < 0 { return r7 } 189 } 190 return *pos 191} 192 193// Compile-only smoke. 194func main() -> i64 { 195 let buf: *u8 = sys_mmap(512) 196 let body: *u8 = "Hello, Nishi!" 197 let written: i64 = http_build_response(buf, 512, 200, 198 "text/plain", body, 13) 199 if written <= 0 { return 1 } 200 // First 9 bytes should be "HTTP/1.1 ". 201 if buf[0] != 0x48 { return 2 } // 'H' 202 if buf[5] != 0x2F { return 3 } // '/' 203 return 0 204}