module Http { import Std::Array::{Array, Array_Len, Array_Get}; import Std::String::{String_Eq, String_EndsWith, String_Contains, String_Len, String_StartsWith}; pub enum HttpMethod { GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS, UNKNOWN, } pub struct HeaderEntry { key: String; value: String; } pub struct HttpRequest { method: HttpMethod; path: String; version: String; body: String; headers: Array; } pub struct HttpResponse { statusCode: int; contentType: String; body: String; extraHeaders: String; } pub func Http_StatusText(code: int) -> String { if code == 200 { return "OK"; } if code == 201 { return "Created"; } if code == 204 { return "No Content"; } if code == 301 { return "Moved Permanently"; } if code == 302 { return "Found"; } if code == 304 { return "Not Modified"; } if code == 400 { return "Bad Request"; } if code == 401 { return "Unauthorized"; } if code == 403 { return "Forbidden"; } if code == 404 { return "Not Found"; } if code == 405 { return "Method Not Allowed"; } if code == 413 { return "Payload Too Large"; } if code == 414 { return "URI Too Long"; } if code == 500 { return "Internal Server Error"; } if code == 501 { return "Not Implemented"; } if code == 503 { return "Service Unavailable"; } return "Unknown"; } pub func Http_MimeType(path: String) -> String { if String_EndsWith(path, ".html") || String_EndsWith(path, ".htm") { return "text/html; charset=utf-8"; } if String_EndsWith(path, ".css") { return "text/css; charset=utf-8"; } if String_EndsWith(path, ".js") { return "application/javascript; charset=utf-8"; } if String_EndsWith(path, ".json") { return "application/json; charset=utf-8"; } if String_EndsWith(path, ".xml") { return "application/xml; charset=utf-8"; } if String_EndsWith(path, ".txt") { return "text/plain; charset=utf-8"; } if String_EndsWith(path, ".png") { return "image/png"; } if String_EndsWith(path, ".jpg") || String_EndsWith(path, ".jpeg") { return "image/jpeg"; } if String_EndsWith(path, ".gif") { return "image/gif"; } if String_EndsWith(path, ".svg") { return "image/svg+xml"; } if String_EndsWith(path, ".ico") { return "image/x-icon"; } if String_EndsWith(path, ".webp") { return "image/webp"; } if String_EndsWith(path, ".woff2") { return "font/woff2"; } if String_EndsWith(path, ".woff") { return "font/woff"; } if String_EndsWith(path, ".wasm") { return "application/wasm"; } return "application/octet-stream"; } pub func Http_MethodName(m: HttpMethod) -> String { match m { HttpMethod::GET => "GET", HttpMethod::POST => "POST", HttpMethod::PUT => "PUT", HttpMethod::DELETE => "DELETE", HttpMethod::PATCH => "PATCH", HttpMethod::HEAD => "HEAD", HttpMethod::OPTIONS => "OPTIONS", HttpMethod::UNKNOWN => "UNKNOWN", } } pub func Http_NewResponse(code: int, contentType: String, body: String) -> HttpResponse { var resp: HttpResponse; resp.statusCode = code; resp.contentType = contentType; resp.body = body; resp.extraHeaders = ""; return resp; } func CharLower(c: int) -> int { if c >= 65 && c <= 90 { return c + 32; } return c; } /// Case-insensitive string equality (HTTP header names/values). pub func String_EqIgnoreCase(a: String, b: String) -> bool { let la: uint = String_Len(a); let lb: uint = String_Len(b); if la != lb { return false; } var i: uint = 0; while i < la { if CharLower(a[i] as int) != CharLower(b[i] as int) { return false; } i = i + 1; } return true; } pub func RequestHeader_Get(req: *HttpRequest, key: String) -> String { // Index-based walk — for-in over Array can corrupt string fields. let n: uint = Array_Len(&req.headers); var i: uint = 0; while i < n { let entry: HeaderEntry = Array_Get(&req.headers, i); if String_EqIgnoreCase(entry.key, key) { return entry.value; } i = i + 1; } return ""; } /// HTTP/1.1 defaults to keep-alive; Connection: close forces close; /// HTTP/1.0 needs explicit keep-alive. Uses parsed headers (safe after /// Parser field-move ownership handoff). pub func Request_WantsKeepAlive(req: *HttpRequest) -> bool { let conn: String = RequestHeader_Get(req, "Connection"); if String_EqIgnoreCase(conn, "close") { return false; } if String_EqIgnoreCase(conn, "keep-alive") { return true; } if String_StartsWith(req.version, "HTTP/1.0") { return false; } return true; } /// Fallback when only raw bytes are available. pub func RawRequest_WantsKeepAlive(raw: String) -> bool { if String_Contains(raw, "Connection: close") || String_Contains(raw, "connection: close") || String_Contains(raw, "CONNECTION: CLOSE") { return false; } if String_Contains(raw, "HTTP/1.0") { if String_Contains(raw, "Connection: keep-alive") || String_Contains(raw, "Connection: Keep-Alive") || String_Contains(raw, "connection: keep-alive") { return true; } return false; } return true; } }