aaeb01e518
- 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
177 lines
5.1 KiB
Plaintext
177 lines
5.1 KiB
Plaintext
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;
|
|
}
|
|
|
|
}
|