code wiki / (root) / http_resp.nx

http_resp.nx source

↩ module page · 198 lines · 7238 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 31import "syscalls.nx" 32 33const HRR_ERR_OVERFLOW: i64 = -1 34 35// Common HTTP status codes + reason phrases. Callers pass the 36// numeric status; we map to a phrase string. Missing codes 37// fall through to "Unknown Status" which is technically legal 38// HTTP (RFC 7230 §3.1.2: reason phrase is "essentially ignored 39// by the recipient"). 40 41func http_reason(code: i64) -> *u8 { 42 if code == 200 { return "OK" } 43 if code == 201 { return "Created" } 44 if code == 204 { return "No Content" } 45 if code == 301 { return "Moved Permanently" } 46 if code == 302 { return "Found" } 47 if code == 304 { return "Not Modified" } 48 if code == 400 { return "Bad Request" } 49 if code == 401 { return "Unauthorized" } 50 if code == 403 { return "Forbidden" } 51 if code == 404 { return "Not Found" } 52 if code == 405 { return "Method Not Allowed" } 53 if code == 413 { return "Payload Too Large" } 54 if code == 429 { return "Too Many Requests" } 55 if code == 500 { return "Internal Server Error" } 56 if code == 501 { return "Not Implemented" } 57 if code == 503 { return "Service Unavailable" } 58 return "Unknown Status" 59} 60 61// Append a null-terminated cstring to out at *pos. Bounds-check 62// against cap. Returns 0 on success, HRR_ERR_OVERFLOW on 63// overflow. 64func http_append_cstr(out: *u8, pos: *i64, cap: i64, s: *u8) -> i64 { 65 let p0: i64 = *pos 66 var i: i64 = 0 67 while s[i] != 0 { 68 if p0 + i >= cap { return HRR_ERR_OVERFLOW } 69 out[p0 + i] = s[i] 70 i = i + 1 71 } 72 *pos = p0 + i 73 return 0 74} 75 76// Append n bytes from src. 77func http_append_bytes(out: *u8, pos: *i64, cap: i64, 78 src: *u8, n: i64) -> i64 { 79 let p0: i64 = *pos 80 if p0 + n > cap { return HRR_ERR_OVERFLOW } 81 var i: i64 = 0 82 while i < n { out[p0 + i] = src[i]; i = i + 1 } 83 *pos = p0 + n 84 return 0 85} 86 87// Append a decimal-formatted i64 (non-negative). 88func http_append_dec(out: *u8, pos: *i64, cap: i64, n: i64) -> i64 { 89 let p0: i64 = *pos 90 if n == 0 { 91 if p0 >= cap { return HRR_ERR_OVERFLOW } 92 out[p0] = 0x30 93 *pos = p0 + 1 94 return 0 95 } 96 let scratch: *u8 = sys_mmap(32) 97 var v: i64 = n 98 var k: i64 = 0 99 while v > 0 { 100 scratch[k] = 0x30 + (v % 10) 101 v = v / 10 102 k = k + 1 103 } 104 if p0 + k > cap { return HRR_ERR_OVERFLOW } 105 var i: i64 = 0 106 while i < k { 107 out[p0 + i] = scratch[k - 1 - i] 108 i = i + 1 109 } 110 *pos = p0 + k 111 return 0 112} 113 114// Emit status line. "HTTP/1.1 <code> <reason>\r\n". 115func http_emit_status(out: *u8, pos: *i64, cap: i64, code: i64) -> i64 { 116 let rc0: i64 = http_append_cstr(out, pos, cap, "HTTP/1.1 ") 117 if rc0 < 0 { return rc0 } 118 let rc1: i64 = http_append_dec(out, pos, cap, code) 119 if rc1 < 0 { return rc1 } 120 let rc2: i64 = http_append_cstr(out, pos, cap, " ") 121 if rc2 < 0 { return rc2 } 122 let rc3: i64 = http_append_cstr(out, pos, cap, http_reason(code)) 123 if rc3 < 0 { return rc3 } 124 let rc4: i64 = http_append_cstr(out, pos, cap, "\r\n") 125 return rc4 126} 127 128// Emit "Name: value\r\n". `value` is a null-terminated cstring. 129func http_emit_header(out: *u8, pos: *i64, cap: i64, 130 name: *u8, value: *u8) -> i64 { 131 let rc0: i64 = http_append_cstr(out, pos, cap, name) 132 if rc0 < 0 { return rc0 } 133 let rc1: i64 = http_append_cstr(out, pos, cap, ": ") 134 if rc1 < 0 { return rc1 } 135 let rc2: i64 = http_append_cstr(out, pos, cap, value) 136 if rc2 < 0 { return rc2 } 137 return http_append_cstr(out, pos, cap, "\r\n") 138} 139 140// Emit "Content-Length: N\r\n" with numeric value. 141func http_emit_content_length(out: *u8, pos: *i64, cap: i64, 142 n: i64) -> i64 { 143 let rc0: i64 = http_append_cstr(out, pos, cap, "Content-Length: ") 144 if rc0 < 0 { return rc0 } 145 let rc1: i64 = http_append_dec(out, pos, cap, n) 146 if rc1 < 0 { return rc1 } 147 return http_append_cstr(out, pos, cap, "\r\n") 148} 149 150// Build a complete response: status + minimal headers + body. 151// Emitted headers: 152// Server: nishi 153// Connection: close 154// Content-Type: <content_type> (caller-supplied cstring) 155// Content-Length: <body_len> 156// Returns total bytes written or HRR_ERR_OVERFLOW. 157func http_build_response(out: *u8, cap: i64, 158 status_code: i64, 159 content_type: *u8, 160 body: *u8, body_len: i64) -> i64 { 161 let pos_raw: *u8 = sys_mmap(16) 162 let pos: *i64 = pos_raw as *i64 163 *pos = 0 164 165 let r1: i64 = http_emit_status(out, pos, cap, status_code) 166 if r1 < 0 { return r1 } 167 let r2: i64 = http_emit_header(out, pos, cap, "Server", "nishi") 168 if r2 < 0 { return r2 } 169 let r3: i64 = http_emit_header(out, pos, cap, "Connection", "close") 170 if r3 < 0 { return r3 } 171 let r4: i64 = http_emit_header(out, pos, cap, "Content-Type", 172 content_type) 173 if r4 < 0 { return r4 } 174 let r5: i64 = http_emit_content_length(out, pos, cap, body_len) 175 if r5 < 0 { return r5 } 176 // Blank line terminates headers. 177 let r6: i64 = http_append_cstr(out, pos, cap, "\r\n") 178 if r6 < 0 { return r6 } 179 // Body. 180 if body_len > 0 { 181 let r7: i64 = http_append_bytes(out, pos, cap, body, body_len) 182 if r7 < 0 { return r7 } 183 } 184 return *pos 185} 186 187// Compile-only smoke. 188func main() -> i64 { 189 let buf: *u8 = sys_mmap(512) 190 let body: *u8 = "Hello, Nishi!" 191 let written: i64 = http_build_response(buf, 512, 200, 192 "text/plain", body, 13) 193 if written <= 0 { return 1 } 194 // First 9 bytes should be "HTTP/1.1 ". 195 if buf[0] != 0x48 { return 2 } // 'H' 196 if buf[5] != 0x2F { return 3 } // '/' 197 return 0 198}