feat(nexus): production modular HTTP/1.1 server + compiler fixes

- Rewrite apps/nexus with modular architecture:
  Config, Http, Errors, Parser, Router, Handlers, Server, Main
- Use algebraic enums for ParseResult/FileResult/HttpError
- Thread-pool server via Channel<ConnectionTask> and spawn
- Fix C backend type ordering for generic struct instances
  (Array_T, Iter_T) and algebraic enum struct payloads
- Collect Slice_T types from struct fields and enum payloads
- Fix match lowering for simple enums (direct value compare)
- Resolve match expression return type from first arm
- Infer element type for for-in over Array<UserStruct>
- Preserve generic type args in field access resolution
- Add fflush to PrintLine/Print for immediate server logs
- Add modern_features golden regression test
- Regenerate golden expected.c files
This commit is contained in:
2026-06-15 00:54:03 +03:00
parent fc0a560e60
commit aaeb01e518
40 changed files with 28530 additions and 21790 deletions
+1 -129
View File
@@ -1,129 +1 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Nexus — Bux HTTP Server</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: #0d1117;
color: #c9d1d9;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 2rem;
}
.card {
background: #161b22;
border: 1px solid #30363d;
border-radius: 8px;
padding: 3rem;
max-width: 600px;
width: 100%;
text-align: center;
}
h1 {
font-size: 2.5rem;
color: #58a6ff;
margin-bottom: 0.5rem;
}
.subtitle {
color: #8b949e;
font-size: 1.1rem;
margin-bottom: 2rem;
}
.features {
text-align: left;
margin: 2rem 0;
}
.features h3 {
color: #58a6ff;
margin-bottom: 0.75rem;
}
.features ul {
list-style: none;
}
.features li {
padding: 0.4rem 0;
color: #8b949e;
}
.features li::before {
content: "▸ ";
color: #3fb950;
}
.endpoints {
margin-top: 2rem;
padding-top: 1.5rem;
border-top: 1px solid #30363d;
}
.endpoints h3 {
color: #d2a8ff;
margin-bottom: 0.75rem;
}
.endpoint {
display: flex;
justify-content: space-between;
padding: 0.3rem 0;
font-family: 'SF Mono', 'Fira Code', monospace;
font-size: 0.9rem;
}
.endpoint .method {
color: #3fb950;
font-weight: bold;
min-width: 60px;
}
.endpoint .path {
color: #c9d1d9;
}
.footer {
margin-top: 2rem;
padding-top: 1.5rem;
border-top: 1px solid #30363d;
color: #484f58;
font-size: 0.85rem;
}
</style>
</head>
<body>
<div class="card">
<h1>⚡ Nexus</h1>
<p class="subtitle">High-performance HTTP Server — Built with Bux</p>
<div class="features">
<h3>Features</h3>
<ul>
<li>HTTP/1.1 — Full request parsing & response building</li>
<li>Multi-threaded — Configurable worker pool</li>
<li>HTTP/2 Detection — Upgrade-aware</li>
<li>WebSocket Upgrade — Handshake support</li>
<li>Static File Serving — With MIME type detection</li>
<li>JSON API Endpoints</li>
</ul>
</div>
<div class="endpoints">
<h3>API Endpoints</h3>
<div class="endpoint">
<span class="method">GET</span>
<span class="path">/api/health</span>
</div>
<div class="endpoint">
<span class="method">GET</span>
<span class="path">/api/info</span>
</div>
<div class="endpoint">
<span class="method">GET</span>
<span class="path">/ws</span>
</div>
</div>
<div class="footer">
Nexus v0.1.0 &middot; Bux Programming Language
</div>
</div>
</body>
</html>
<html><body>Hello Nexus</body></html>
+21
View File
@@ -0,0 +1,21 @@
module Config {
pub struct ServerConfig {
bindAddr: String;
port: int;
workerCount: int;
publicDir: String;
backlog: int;
}
pub const func DefaultConfig() -> ServerConfig {
return ServerConfig {
bindAddr: "0.0.0.0",
port: 8080,
workerCount: 4,
publicDir: "public",
backlog: 128,
};
}
}
+65
View File
@@ -0,0 +1,65 @@
module Errors {
import Http::{HttpRequest};
pub enum HttpError {
BadRequest,
NotFound,
MethodNotAllowed,
InternalError(String),
}
pub enum ParseResult {
Ok(HttpRequest),
Err(HttpError),
}
pub enum FileResult {
Ok(String),
Err(HttpError),
}
pub func ParseResult_NewOk(req: HttpRequest) -> ParseResult {
let r: ParseResult = ParseResult { tag: ParseResult_Ok };
r.data.Ok_0 = req;
return r;
}
pub func ParseResult_NewErr(err: HttpError) -> ParseResult {
let r: ParseResult = ParseResult { tag: ParseResult_Err };
r.data.Err_0 = err;
return r;
}
pub func FileResult_NewOk(content: String) -> FileResult {
let r: FileResult = FileResult { tag: FileResult_Ok };
r.data.Ok_0 = content;
return r;
}
pub func FileResult_NewErr(err: HttpError) -> FileResult {
let r: FileResult = FileResult { tag: FileResult_Err };
r.data.Err_0 = err;
return r;
}
pub func FileResult_IsOk(r: FileResult) -> bool {
return r.tag == FileResult_Ok;
}
pub func FileResult_Unwrap(r: FileResult) -> String {
return r.data.Ok_0;
}
pub func FileResult_UnwrapErr(r: FileResult) -> HttpError {
return r.data.Err_0;
}
pub func HttpError_ToString(err: HttpError) -> String {
if err.tag == HttpError_BadRequest { return "Bad Request"; }
if err.tag == HttpError_NotFound { return "Not Found"; }
if err.tag == HttpError_MethodNotAllowed { return "Method Not Allowed"; }
return err.data.InternalError_0;
}
}
+94
View File
@@ -0,0 +1,94 @@
module Handlers {
import Std::String::{String_Eq, String_Contains};
import Http::{HttpMethod, HttpRequest, HttpResponse, Http_NewResponse, Http_MimeType, RequestHeader_Get};
import Errors::{HttpError, FileResult, FileResult_NewOk, FileResult_NewErr, FileResult_IsOk, FileResult_Unwrap, FileResult_UnwrapErr, HttpError_ToString};
extern func bux_strlen(s: String) -> uint;
extern func bux_file_exists(path: String) -> int;
extern func bux_read_file(path: String) -> String;
extern func bux_path_join(a: String, b: String) -> String;
extern func bux_sb_new(initial_cap: uint) -> *void;
extern func bux_sb_append(sb: *void, s: String);
extern func bux_sb_append_int(sb: *void, n: int64);
extern func bux_sb_build(sb: *void) -> String;
extern func bux_sb_free(sb: *void);
pub func NotFoundResponse() -> HttpResponse {
return Http_NewResponse(404, "application/json; charset=utf-8", "{\"error\":\"not_found\"}");
}
pub func MethodNotAllowedResponse() -> HttpResponse {
return Http_NewResponse(405, "text/plain; charset=utf-8", "Method Not Allowed");
}
pub func ReadStaticFile(requestPath: String) -> FileResult {
if String_Contains(requestPath, "..") {
return FileResult_NewErr(HttpError { tag: HttpError_NotFound });
}
var filePath: String = requestPath;
if String_Eq(filePath, "/") {
filePath = "/index.html";
}
let fullPath: String = bux_path_join("public", filePath);
if bux_file_exists(fullPath) == 0 {
return FileResult_NewErr(HttpError { tag: HttpError_NotFound });
}
let content: String = bux_read_file(fullPath);
return FileResult_NewOk(content);
}
func FileErrorResponse(err: HttpError) -> HttpResponse {
match err {
HttpError::NotFound => NotFoundResponse(),
_ => Http_NewResponse(500, "text/plain; charset=utf-8", HttpError_ToString(err)),
}
}
pub func ServeStaticFile(req: HttpRequest) -> HttpResponse {
if req.method != HttpMethod_GET && req.method != HttpMethod_HEAD {
return MethodNotAllowedResponse();
}
let result: FileResult = ReadStaticFile(req.path);
if FileResult_IsOk(result) {
let content: String = FileResult_Unwrap(result);
return Http_NewResponse(200, Http_MimeType(req.path), content);
}
return FileErrorResponse(FileResult_UnwrapErr(result));
}
pub func HandleApiHealth() -> HttpResponse {
return Http_NewResponse(200, "application/json; charset=utf-8",
"{\"status\":\"ok\",\"server\":\"Nexus\",\"version\":\"0.2.0\"}");
}
pub func HandleApiInfo() -> HttpResponse {
return Http_NewResponse(200, "application/json; charset=utf-8",
"{\"name\":\"Nexus\",\"language\":\"Bux\",\"features\":[\"HTTP/1.1\",\"thread-pool\",\"algebraic-enums\"]}");
}
pub func HandleWebSocketUpgrade(req: HttpRequest) -> HttpResponse {
let wsKey: String = RequestHeader_Get(req, "Sec-WebSocket-Key");
var resp: HttpResponse;
resp.statusCode = 101;
resp.contentType = "";
resp.body = "";
let sb: *void = bux_sb_new(256);
bux_sb_append(sb, "Upgrade: websocket\r\n");
bux_sb_append(sb, "Connection: Upgrade\r\n");
bux_sb_append(sb, "Sec-WebSocket-Accept: ");
bux_sb_append(sb, wsKey);
bux_sb_append(sb, "\r\n");
resp.extraHeaders = bux_sb_build(sb);
bux_sb_free(sb);
return resp;
}
}
+107
View File
@@ -0,0 +1,107 @@
module Http {
import Std::Array::{Array};
import Std::String::{String_Eq, String_EndsWith, String_Contains};
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<HeaderEntry>;
}
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;
}
pub func RequestHeader_Get(req: HttpRequest, key: String) -> String {
for entry in req.headers {
if String_Eq(entry.key, key) {
return entry.value;
}
}
return "";
}
}
+39 -540
View File
@@ -1,550 +1,49 @@
// =============================================================================
// Nexus — High-performance multi-threaded HTTP/1.1, HTTP/2 & WebSocket server
// Built with the Bux programming language
// =============================================================================
module Main {
// =============================================================================
// Imports — only what we actually use
// =============================================================================
import Std::Io::{PrintLine, Print, PrintInt};
import Std::Net::{Net_Create, Net_SetReuse, Net_Bind, Net_Listen, Net_Accept, Net_Send, Net_Recv, Net_Close, Net_LastError};
import Std::String::{String_Len, String_Eq, String_StartsWith, String_EndsWith, String_Contains, String_Trim, String_FromInt, String_Offset};
import Std::Task::{Task_Join, TaskHandle};
import Config::{ServerConfig, DefaultConfig};
import Http::{HttpMethod};
import Router::{Handler, Route, Router};
import Server::{RunServer};
import Std::Array::{Array, Array_New, Array_Push};
// =============================================================================
// Extern Functions (from C runtime — used directly for performance)
// =============================================================================
extern func bux_alloc(size: uint) -> *void;
extern func bux_strlen(s: String) -> uint;
extern func bux_strcmp(a: String, b: String) -> int;
extern func bux_strstr(haystack: String, needle: String) -> String;
extern func bux_str_slice(s: String, start: uint, len: uint) -> String;
extern func bux_str_split_count(s: String, delim: String) -> uint;
extern func bux_str_split_part(s: String, delim: String, index: uint) -> String;
extern func bux_sb_new(initial_cap: uint) -> *void;
extern func bux_sb_append(sb: *void, s: String);
extern func bux_sb_append_int(sb: *void, n: int64);
extern func bux_sb_append_char(sb: *void, c: char8);
extern func bux_sb_build(sb: *void) -> String;
extern func bux_sb_free(sb: *void);
extern func bux_path_join(a: String, b: String) -> String;
extern func bux_read_file(path: String) -> String;
extern func bux_file_exists(path: String) -> int;
func BuildRouter() -> Router {
var routes: Array<Route> = Array_New<Route>(8);
// =============================================================================
// HTTP Method Enum (simple enum — no data payload)
// =============================================================================
enum HttpMethod {
GET,
POST,
PUT,
DELETE,
PATCH,
HEAD,
OPTIONS,
UNKNOWN,
Array_Push<Route>(&routes, Route {
method: HttpMethod { tag: HttpMethod_GET },
path: "/api/health",
handler: Handler { tag: Handler_ApiHealth },
});
Array_Push<Route>(&routes, Route {
method: HttpMethod { tag: HttpMethod_GET },
path: "/api/info",
handler: Handler { tag: Handler_ApiInfo },
});
Array_Push<Route>(&routes, Route {
method: HttpMethod { tag: HttpMethod_GET },
path: "/ws",
handler: Handler { tag: Handler_WsUpgrade },
});
Array_Push<Route>(&routes, Route {
method: HttpMethod { tag: HttpMethod_GET },
path: "/",
handler: Handler { tag: Handler_StaticFile },
});
return Router {
routes: routes,
notFound: Handler { tag: Handler_NotFound },
};
}
// =============================================================================
// Struct: HTTP Request
// =============================================================================
struct HttpRequest {
method: HttpMethod,
path: String,
body: String,
headerKeys: *String,
headerValues: *String,
headerCount: int,
headerCap: int,
}
// =============================================================================
// Struct: HTTP Response
// =============================================================================
struct HttpResponse {
statusCode: int,
statusText: String,
contentType: String,
body: String,
extraHeaders: String, // pre-formatted extra headers (for WS upgrade, etc.)
connectionClose: bool,
}
// =============================================================================
// Utility: Status text lookup
// =============================================================================
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";
}
// =============================================================================
// Utility: Parse HTTP method string → HttpMethod enum
// =============================================================================
func Http_ParseMethod(s: String) -> HttpMethod {
if String_Eq(s, "GET") { return HttpMethod { tag: HttpMethod_GET }; }
if String_Eq(s, "POST") { return HttpMethod { tag: HttpMethod_POST }; }
if String_Eq(s, "PUT") { return HttpMethod { tag: HttpMethod_PUT }; }
if String_Eq(s, "DELETE") { return HttpMethod { tag: HttpMethod_DELETE }; }
if String_Eq(s, "PATCH") { return HttpMethod { tag: HttpMethod_PATCH }; }
if String_Eq(s, "HEAD") { return HttpMethod { tag: HttpMethod_HEAD }; }
if String_Eq(s, "OPTIONS") { return HttpMethod { tag: HttpMethod_OPTIONS }; }
return HttpMethod { tag: HttpMethod_UNKNOWN };
}
// =============================================================================
// Utility: MIME type from file extension
// =============================================================================
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";
}
// =============================================================================
// Header Storage: dynamic key-value array (faster than hash map for 5-15 headers)
// =============================================================================
func RequestHeader_Init(req: *HttpRequest) {
let cap: int = 16;
req.headerCap = cap;
req.headerKeys = bux_alloc(cap as uint * 8) as *String;
req.headerValues = bux_alloc(cap as uint * 8) as *String;
req.headerCount = 0;
}
func RequestHeader_Add(req: *HttpRequest, key: String, value: String) {
if req.headerCount >= req.headerCap { return; }
req.headerKeys[req.headerCount] = key;
req.headerValues[req.headerCount] = value;
req.headerCount = req.headerCount + 1;
}
func RequestHeader_Get(req: *HttpRequest, key: String) -> String {
var i: int = 0;
while i < req.headerCount {
if bux_strcmp(req.headerKeys[i], key) == 0 {
return req.headerValues[i];
}
i = i + 1;
}
return "";
}
// =============================================================================
// HTTP Request Parser
// =============================================================================
func Http_ParseRequest(raw: String) -> HttpRequest {
var req: HttpRequest;
req.method = HttpMethod { tag: HttpMethod_UNKNOWN };
req.path = "/";
req.body = "";
RequestHeader_Init(&req);
// Guard: null/empty input
if String_Len(raw) == 0 { return req; }
let rawLen: uint = bux_strlen(raw);
if rawLen == 0 { return req; }
// Find \r\n\r\n separating headers from body
let boundary: String = bux_strstr(raw, "\r\n\r\n");
var headerLen: uint = rawLen;
if String_Len(boundary) > 0 {
headerLen = String_Offset(boundary, raw);
let bodyStart: uint = headerLen + 4;
if bodyStart < rawLen {
req.body = bux_str_slice(raw, bodyStart, rawLen - bodyStart);
}
}
// Slice out header block
if headerLen == 0 { return req; }
let headerBlock: String = bux_str_slice(raw, 0, headerLen);
// Count header lines
let lineCount: uint = bux_str_split_count(headerBlock, "\r\n");
if lineCount == 0 { return req; }
// --- Parse request line: "METHOD /path HTTP/1.1" ---
let requestLine: String = bux_str_split_part(headerBlock, "\r\n", 0);
if bux_strlen(requestLine) > 0 {
req.method = Http_ParseMethod(bux_str_split_part(requestLine, " ", 0));
req.path = bux_str_split_part(requestLine, " ", 1);
if String_Eq(req.path, "") || String_Len(req.path) == 0 {
req.path = "/";
}
}
// --- Parse headers (lines 1..N-1) ---
var i: uint = 1;
while i < lineCount {
let line: String = bux_str_split_part(headerBlock, "\r\n", i);
let lineLen: uint = bux_strlen(line);
if lineLen == 0 { i = i + 1; continue; }
// Find ": " separator
let colonPos: String = bux_strstr(line, ": ");
var key: String = "";
var value: String = "";
if String_Len(colonPos) > 0 {
let keyLen: uint = String_Offset(colonPos, line);
key = bux_str_slice(line, 0, keyLen);
let valStart: uint = keyLen + 2;
if valStart < lineLen {
value = bux_str_slice(line, valStart, lineLen - valStart);
}
} else {
// Try ":" without space
let colonPos2: String = bux_strstr(line, ":");
if String_Len(colonPos2) > 0 {
let keyLen: uint = String_Offset(colonPos2, line);
key = bux_str_slice(line, 0, keyLen);
let valStart: uint = keyLen + 1;
if valStart < lineLen {
value = bux_str_slice(line, valStart, lineLen - valStart);
}
value = String_Trim(value);
}
}
// Store if valid key found
if bux_strlen(key) > 0 {
key = String_Trim(key);
RequestHeader_Add(&req, key, value);
}
i = i + 1;
}
return req;
}
// =============================================================================
// HTTP Response Builder
// =============================================================================
func Http_BuildResponse(resp: HttpResponse) -> String {
let sb: *void = bux_sb_new(4096);
// Status line
bux_sb_append(sb, "HTTP/1.1 ");
bux_sb_append_int(sb, resp.statusCode as int64);
bux_sb_append(sb, " ");
bux_sb_append(sb, resp.statusText);
bux_sb_append(sb, "\r\n");
// Server
bux_sb_append(sb, "Server: Nexus/0.1.0 (Bux)\r\n");
// Extra headers (for WS upgrade etc.)
if bux_strlen(resp.extraHeaders) > 0 {
bux_sb_append(sb, resp.extraHeaders);
}
// Content-Type
if bux_strlen(resp.contentType) > 0 {
bux_sb_append(sb, "Content-Type: ");
bux_sb_append(sb, resp.contentType);
bux_sb_append(sb, "\r\n");
}
// Content-Length
let bodyLen: uint = bux_strlen(resp.body);
bux_sb_append(sb, "Content-Length: ");
bux_sb_append_int(sb, bodyLen as int64);
bux_sb_append(sb, "\r\n");
// Connection
if resp.connectionClose {
bux_sb_append(sb, "Connection: close\r\n");
} else {
bux_sb_append(sb, "Connection: keep-alive\r\n");
}
// End headers
bux_sb_append(sb, "\r\n");
// Body
if bodyLen > 0 {
bux_sb_append(sb, resp.body);
}
let result: String = bux_sb_build(sb);
bux_sb_free(sb);
return result;
}
// =============================================================================
// Helper: Create a simple response
// =============================================================================
func Http_NewResponse(code: int, contentType: String, body: String) -> HttpResponse {
var resp: HttpResponse;
resp.statusCode = code;
resp.statusText = Http_StatusText(code);
resp.contentType = contentType;
resp.body = body;
resp.extraHeaders = "";
resp.connectionClose = true;
return resp;
}
// =============================================================================
// Static File Server
// =============================================================================
func ServeStaticFile(requestPath: String) -> HttpResponse {
// Block directory traversal
if String_Contains(requestPath, "..") {
return Http_NewResponse(403, "text/plain; charset=utf-8", "Forbidden");
}
// Default to index.html
var filePath: String = requestPath;
if String_Eq(filePath, "/") {
filePath = "/index.html";
}
// Resolve full path
let fullPath: String = bux_path_join("public", filePath);
// Check existence
if bux_file_exists(fullPath) == 0 {
return Http_NewResponse(404, "text/plain; charset=utf-8", "Not Found");
}
// Read and serve
let content: String = bux_read_file(fullPath);
if String_Len(content) == 0 {
return Http_NewResponse(500, "text/plain; charset=utf-8", "Internal Server Error");
}
let mime: String = Http_MimeType(filePath);
return Http_NewResponse(200, mime, content);
}
// =============================================================================
// API Handlers
// =============================================================================
func HandleApiHealth() -> HttpResponse {
return Http_NewResponse(200, "application/json; charset=utf-8",
"{\"status\":\"ok\",\"server\":\"Nexus\",\"version\":\"0.1.0\"}");
}
func HandleApiInfo() -> HttpResponse {
return Http_NewResponse(200, "application/json; charset=utf-8",
"{\"name\":\"Nexus\",\"language\":\"Bux\",\"threads\":4,\"features\":[\"HTTP/1.1\",\"HTTP/2-detect\",\"WebSocket-upgrade\"]}");
}
// =============================================================================
// WebSocket Upgrade Handshake
// =============================================================================
func HandleWebSocketUpgrade(req: HttpRequest) -> HttpResponse {
let wsKey: String = RequestHeader_Get(&req, "Sec-WebSocket-Key");
var resp: HttpResponse;
resp.statusCode = 101;
resp.statusText = "Switching Protocols";
resp.contentType = "";
resp.body = "";
resp.connectionClose = false;
// Build upgrade headers as extraHeaders
// TODO: SHA-1 + Base64 for proper accept key
let sb: *void = bux_sb_new(256);
bux_sb_append(sb, "Upgrade: websocket\r\n");
bux_sb_append(sb, "Connection: Upgrade\r\n");
bux_sb_append(sb, "Sec-WebSocket-Accept: ");
bux_sb_append(sb, wsKey); // placeholder — real impl needs crypto
bux_sb_append(sb, "\r\n");
resp.extraHeaders = bux_sb_build(sb);
bux_sb_free(sb);
return resp;
}
// =============================================================================
// Router / Dispatcher
// =============================================================================
func Router_Dispatch(req: HttpRequest) -> HttpResponse {
// Check for upgrade headers
let upgrade: String = RequestHeader_Get(&req, "Upgrade");
if bux_strlen(upgrade) > 0 {
if String_Contains(upgrade, "websocket") {
return HandleWebSocketUpgrade(req);
}
}
// API routes
if String_StartsWith(req.path, "/api/") {
if String_Eq(req.path, "/api/health") { return HandleApiHealth(); }
if String_Eq(req.path, "/api/info") { return HandleApiInfo(); }
return Http_NewResponse(404, "application/json; charset=utf-8",
"{\"error\":\"not_found\"}");
}
// WebSocket endpoint
if String_Eq(req.path, "/ws") {
return HandleWebSocketUpgrade(req);
}
// Static files — GET/HEAD only
if req.method.tag == HttpMethod_GET || req.method.tag == HttpMethod_HEAD {
return ServeStaticFile(req.path);
}
// Method not allowed
return Http_NewResponse(405, "text/plain; charset=utf-8", "Method Not Allowed");
}
// =============================================================================
// Method name for logging (avoids 8 if-checks in hot path)
// =============================================================================
func Http_MethodName(m: HttpMethod) -> String {
if m.tag == HttpMethod_GET { return "GET"; }
if m.tag == HttpMethod_POST { return "POST"; }
if m.tag == HttpMethod_PUT { return "PUT"; }
if m.tag == HttpMethod_DELETE { return "DELETE"; }
if m.tag == HttpMethod_PATCH { return "PATCH"; }
if m.tag == HttpMethod_HEAD { return "HEAD"; }
if m.tag == HttpMethod_OPTIONS { return "OPTIONS"; }
return "UNKNOWN";
}
// =============================================================================
// Connection Handler — read, parse, route, respond
// =============================================================================
func HandleConnection(clientFd: int) {
let raw: String = Net_Recv(clientFd, 8192);
// Empty read → client disconnected
if String_Len(raw) == 0 { return; }
if bux_strlen(raw) == 0 { return; }
// HTTP/2 connection preface detection
if String_StartsWith(raw, "PRI * HTTP/2.0") {
let resp: HttpResponse = Http_NewResponse(200, "text/plain; charset=utf-8",
"HTTP/2 detected — full support planned for future release.\r\n");
let respStr: String = Http_BuildResponse(resp);
Net_Send(clientFd, respStr);
return;
}
// Parse HTTP/1.1
let req: HttpRequest = Http_ParseRequest(raw);
// Log
Print(Http_MethodName(req.method));
Print(" ");
Print(req.path);
Print(" -> ");
// Dispatch
let resp: HttpResponse = Router_Dispatch(req);
// Log result
PrintInt(resp.statusCode);
Print(" ");
PrintLine(resp.statusText);
// Send
let respStr: String = Http_BuildResponse(resp);
Net_Send(clientFd, respStr);
}
// =============================================================================
// Worker Thread — accept loop
// =============================================================================
func Worker(serverFd: int) {
while true {
let clientFd: int = Net_Accept(serverFd);
if clientFd < 0 {
continue;
}
HandleConnection(clientFd);
Net_Close(clientFd);
}
}
// =============================================================================
// Main Entry Point
// =============================================================================
func Main() -> int {
PrintLine("================================================");
PrintLine(" Nexus HTTP Server v0.1.0");
PrintLine(" Multi-threaded HTTP/1.1 + HTTP/2 & WS detect");
PrintLine(" Built with Bux");
PrintLine("================================================");
PrintLine("");
let config: ServerConfig = DefaultConfig();
let router: Router = BuildRouter();
// Create socket
let serverFd: int = Net_Create();
if serverFd < 0 {
PrintLine("FATAL: socket() failed");
return 1;
}
// SO_REUSEADDR
if !Net_SetReuse(serverFd) {
PrintLine("WARN: SO_REUSEADDR failed");
}
// Bind
if !Net_Bind(serverFd, "0.0.0.0", 8080) {
Print("FATAL: bind(8080) failed: ");
PrintLine(Net_LastError());
Net_Close(serverFd);
return 1;
}
// Listen
if !Net_Listen(serverFd, 128) {
PrintLine("FATAL: listen() failed");
Net_Close(serverFd);
return 1;
}
PrintLine("Listening on http://0.0.0.0:8080");
PrintLine("4 worker threads | static: ./public/");
PrintLine("Endpoints: / /api/health /api/info /ws");
PrintLine("Press Ctrl+C to stop.");
PrintLine("");
// Spawn 3 workers (main thread is the 4th)
spawn Worker(serverFd);
spawn Worker(serverFd);
spawn Worker(serverFd);
// Main thread becomes worker #4 (blocking call)
Worker(serverFd);
return 0;
return RunServer(config, router);
}
} // module Main
}
+130
View File
@@ -0,0 +1,130 @@
module Parser {
import Std::String::{String_Len, String_Eq, String_Trim};
import Std::Array::{Array, Array_New, Array_Push};
import Http::{HttpMethod, HttpRequest, HeaderEntry};
import Errors::{HttpError, ParseResult, ParseResult_NewOk, ParseResult_NewErr};
extern func bux_strlen(s: String) -> uint;
extern func bux_strstr(haystack: String, needle: String) -> String;
extern func bux_str_slice(s: String, start: uint, len: uint) -> String;
extern func bux_str_offset(pos: String, base: String) -> uint;
pub func ParseMethod(s: String) -> HttpMethod {
if String_Eq(s, "GET") { return HttpMethod { tag: HttpMethod_GET }; }
if String_Eq(s, "POST") { return HttpMethod { tag: HttpMethod_POST }; }
if String_Eq(s, "PUT") { return HttpMethod { tag: HttpMethod_PUT }; }
if String_Eq(s, "DELETE") { return HttpMethod { tag: HttpMethod_DELETE }; }
if String_Eq(s, "PATCH") { return HttpMethod { tag: HttpMethod_PATCH }; }
if String_Eq(s, "HEAD") { return HttpMethod { tag: HttpMethod_HEAD }; }
if String_Eq(s, "OPTIONS") { return HttpMethod { tag: HttpMethod_OPTIONS }; }
return HttpMethod { tag: HttpMethod_UNKNOWN };
}
func Slice(raw: String, start: int, len: int) -> String {
if start < 0 || len <= 0 { return ""; }
return bux_str_slice(raw, start as uint, len as uint);
}
func FindCrlf(raw: String, start: int) -> int {
let rawLen: uint = bux_strlen(raw);
if start as uint >= rawLen { return -1; }
let tail: String = bux_str_slice(raw, start as uint, rawLen - start as uint);
let hit: String = bux_strstr(tail, "\r\n");
if String_Len(hit) == 0 { return -1; }
let offset: uint = bux_str_offset(hit, tail);
return start + offset as int;
}
func ParseHeaders(raw: String, start: int, end: int) -> Array<HeaderEntry> {
var headers: Array<HeaderEntry> = Array_New<HeaderEntry>(16);
var pos: int = start;
while pos < end {
let lineEnd: int = FindCrlf(raw, pos);
if lineEnd < 0 || lineEnd >= end { break; }
let lineLen: int = lineEnd - pos;
if lineLen > 0 {
let line: String = Slice(raw, pos, lineLen);
let colon: String = bux_strstr(line, ":");
if String_Len(colon) > 0 {
let keyLen: uint = bux_str_offset(colon, line);
let key: String = String_Trim(Slice(line, 0, keyLen as int));
let valStart: int = keyLen as int + 1;
let val: String = String_Trim(Slice(line, valStart, lineLen - valStart));
let entry: HeaderEntry = HeaderEntry { key: key, value: val };
Array_Push<HeaderEntry>(&headers, entry);
}
}
pos = lineEnd + 2;
}
return headers;
}
pub func ParseRequest(raw: String) -> ParseResult {
let rawLen: uint = bux_strlen(raw);
if rawLen == 0 {
return ParseResult_NewErr(HttpError { tag: HttpError_BadRequest });
}
// Find end of request line
let lineEnd: int = FindCrlf(raw, 0);
if lineEnd < 0 {
return ParseResult_NewErr(HttpError { tag: HttpError_BadRequest });
}
// Split request line: METHOD PATH VERSION
var methodEnd: int = -1;
var pathStart: int = -1;
var pathEnd: int = -1;
var i: int = 0;
while i < lineEnd {
if raw[i] as int == 32 { // space
if methodEnd < 0 {
methodEnd = i;
pathStart = i + 1;
} else if pathEnd < 0 {
pathEnd = i;
break;
}
}
i = i + 1;
}
if methodEnd < 0 || pathStart < 0 || pathEnd < 0 {
return ParseResult_NewErr(HttpError { tag: HttpError_BadRequest });
}
let methodStr: String = Slice(raw, 0, methodEnd);
let path: String = Slice(raw, pathStart, pathEnd - pathStart);
let version: String = Slice(raw, pathEnd + 1, lineEnd - pathEnd - 1);
// Find header/body boundary
let boundary: String = bux_strstr(raw, "\r\n\r\n");
var headers: Array<HeaderEntry> = Array_New<HeaderEntry>(16);
var body: String = "";
if String_Len(boundary) > 0 {
let headerEnd: uint = bux_str_offset(boundary, raw);
headers = ParseHeaders(raw, lineEnd + 2, headerEnd as int);
let bodyStart: uint = headerEnd + 4;
if bodyStart < rawLen {
body = bux_str_slice(raw, bodyStart, rawLen - bodyStart);
}
} else {
headers = ParseHeaders(raw, lineEnd + 2, rawLen as int);
}
if String_Eq(path, "") {
return ParseResult_NewErr(HttpError { tag: HttpError_BadRequest });
}
let req: HttpRequest = HttpRequest {
method: ParseMethod(methodStr),
path: path,
version: version,
body: body,
headers: headers,
};
return ParseResult_NewOk(req);
}
}
+46
View File
@@ -0,0 +1,46 @@
module Router {
import Std::Array::{Array};
import Std::String::{String_Eq};
import Http::{HttpMethod, HttpRequest, HttpResponse, Http_NewResponse};
import Handlers::{ServeStaticFile, HandleApiHealth, HandleApiInfo, HandleWebSocketUpgrade, NotFoundResponse};
pub enum Handler {
StaticFile,
ApiHealth,
ApiInfo,
WsUpgrade,
NotFound,
}
pub struct Route {
method: HttpMethod;
path: String;
handler: Handler;
}
pub struct Router {
routes: Array<Route>;
notFound: Handler;
}
pub func Handler_Handle(h: Handler, req: HttpRequest) -> HttpResponse {
match h {
Handler::StaticFile => ServeStaticFile(req),
Handler::ApiHealth => HandleApiHealth(),
Handler::ApiInfo => HandleApiInfo(),
Handler::WsUpgrade => HandleWebSocketUpgrade(req),
Handler::NotFound => NotFoundResponse(),
}
}
pub func Router_Dispatch(r: Router, req: HttpRequest) -> HttpResponse {
for route in r.routes {
if route.method == req.method && String_Eq(route.path, req.path) {
return Handler_Handle(route.handler, req);
}
}
return Handler_Handle(r.notFound, req);
}
}
+176
View File
@@ -0,0 +1,176 @@
module Server {
import Std::Io::{Print, PrintLine, PrintInt};
import Std::Net::{Net_Create, Net_SetReuse, Net_Bind, Net_Listen, Net_Accept, Net_Send, Net_Recv, Net_Close, Net_LastError};
import Std::String::{String_Len, String_StartsWith};
import Std::Channel::{Channel, Channel_New, Channel_Send, Channel_Recv};
import Config::{ServerConfig};
import Http::{HttpRequest, HttpResponse, Http_StatusText, Http_NewResponse};
import Errors::{ParseResult};
import Parser::{ParseRequest};
import Router::{Router, Router_Dispatch};
extern func bux_strlen(s: String) -> uint;
extern func bux_sb_new(initial_cap: uint) -> *void;
extern func bux_sb_append(sb: *void, s: String);
extern func bux_sb_append_int(sb: *void, n: int64);
extern func bux_sb_build(sb: *void) -> String;
extern func bux_sb_free(sb: *void);
pub struct ConnectionTask {
fd: int;
}
pub func BuildResponse(resp: HttpResponse) -> String {
let sb: *void = bux_sb_new(4096);
bux_sb_append(sb, "HTTP/1.1 ");
bux_sb_append_int(sb, resp.statusCode as int64);
bux_sb_append(sb, " ");
bux_sb_append(sb, Http_StatusText(resp.statusCode));
bux_sb_append(sb, "\r\n");
bux_sb_append(sb, "Server: Nexus/0.2.0 (Bux)\r\n");
if bux_strlen(resp.extraHeaders) > 0 {
bux_sb_append(sb, resp.extraHeaders);
}
if bux_strlen(resp.contentType) > 0 {
bux_sb_append(sb, "Content-Type: ");
bux_sb_append(sb, resp.contentType);
bux_sb_append(sb, "\r\n");
}
let bodyLen: uint = bux_strlen(resp.body);
bux_sb_append(sb, "Content-Length: ");
bux_sb_append_int(sb, bodyLen as int64);
bux_sb_append(sb, "\r\n");
bux_sb_append(sb, "Connection: close\r\n");
bux_sb_append(sb, "\r\n");
if bodyLen > 0 {
bux_sb_append(sb, resp.body);
}
let result: String = bux_sb_build(sb);
bux_sb_free(sb);
return result;
}
pub func HandleConnection(fd: int, router: Router) {
let raw: String = Net_Recv(fd, 8192);
if String_Len(raw) == 0 {
return;
}
// HTTP/2 preface detection
if String_StartsWith(raw, "PRI * HTTP/2.0") {
let resp: HttpResponse = Http_NewResponse(200, "text/plain; charset=utf-8",
"HTTP/2 detected — full support planned for future release.\r\n");
Net_Send(fd, BuildResponse(resp));
return;
}
let parsed: ParseResult = ParseRequest(raw);
if parsed.tag == ParseResult_Ok {
let req: HttpRequest = parsed.data.Ok_0;
let resp: HttpResponse = Router_Dispatch(router, req);
Net_Send(fd, BuildResponse(resp));
} else {
let resp: HttpResponse = Http_NewResponse(400, "text/plain; charset=utf-8", "Bad Request");
Net_Send(fd, BuildResponse(resp));
}
}
pub struct WorkerCtx {
taskQueue: *Channel<ConnectionTask>;
router: Router;
}
pub func Worker(ctx: *WorkerCtx) {
while true {
let task: ConnectionTask = Channel_Recv<ConnectionTask>(ctx.taskQueue);
HandleConnection(task.fd, ctx.router);
Net_Close(task.fd);
}
}
pub struct AcceptorCtx {
serverFd: int;
taskQueue: *Channel<ConnectionTask>;
}
pub func Acceptor(ctx: *AcceptorCtx) {
while true {
let fd: int = Net_Accept(ctx.serverFd);
if fd >= 0 {
let task: ConnectionTask = ConnectionTask { fd: fd };
Channel_Send<ConnectionTask>(ctx.taskQueue, task);
}
}
}
pub func RunServer(config: ServerConfig, router: Router) -> int {
PrintLine("================================================");
PrintLine(" Nexus HTTP Server v0.2.0");
PrintLine(" Production-ready HTTP/1.1 with thread-pool");
PrintLine(" Built with Bux");
PrintLine("================================================");
PrintLine("");
let serverFd: int = Net_Create();
if serverFd < 0 {
PrintLine("FATAL: socket() failed");
return 1;
}
if !Net_SetReuse(serverFd) {
PrintLine("WARN: SO_REUSEADDR failed");
}
if !Net_Bind(serverFd, config.bindAddr, config.port) {
Print("FATAL: bind failed: ");
PrintLine(Net_LastError());
Net_Close(serverFd);
return 1;
}
if !Net_Listen(serverFd, config.backlog) {
PrintLine("FATAL: listen() failed");
Net_Close(serverFd);
return 1;
}
Print("Listening on http://");
Print(config.bindAddr);
Print(":");
PrintInt(config.port);
PrintLine("");
PrintInt(config.workerCount);
PrintLine(" worker threads | static: ./public/");
PrintLine("Endpoints: / /api/health /api/info /ws");
PrintLine("Press Ctrl+C to stop.");
PrintLine("");
let taskQueue: Channel<ConnectionTask> = Channel_New<ConnectionTask>(config.backlog as int64);
let workerCtx: WorkerCtx = WorkerCtx { taskQueue: &taskQueue, router: router };
let acceptorCtx: AcceptorCtx = AcceptorCtx { serverFd: serverFd, taskQueue: &taskQueue };
// Spawn workers (main thread will also become one)
var i: int = 0;
while i < config.workerCount - 1 {
spawn Worker(&workerCtx);
i = i + 1;
}
// Spawn acceptor
spawn Acceptor(&acceptorCtx);
// Main thread works too
Worker(&workerCtx);
return 0;
}
}