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:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,365 @@
|
||||
# Nexus HTTP Server Rewrite — Design Spec
|
||||
|
||||
**Date:** 2026-06-14
|
||||
**Goal:** Rewrite `apps/nexus` as a production-ready, modular HTTP/1.1 server that exercises modern Bux language features.
|
||||
|
||||
---
|
||||
|
||||
## 1. Scope
|
||||
|
||||
Keep the same observable behavior as the current Nexus app, but restructure it for production quality:
|
||||
|
||||
- HTTP/1.1 request parsing
|
||||
- Static file serving from `./public/`
|
||||
- API endpoints: `GET /api/health`, `GET /api/info`
|
||||
- WebSocket upgrade detection on `/ws`
|
||||
- Multi-threaded request handling
|
||||
|
||||
What changes:
|
||||
- Monolithic `Main.bux` → modular source tree
|
||||
- Function-pointer dispatch → `interface`-based handlers and middleware
|
||||
- Ad-hoc error handling → algebraic enum `Result`/`Option` patterns
|
||||
- Raw pointer buffer parsing → `@[Checked]` borrow-checked parsing
|
||||
- Direct worker spawn → thread-pool backed by `Channel<ConnectionTask>`
|
||||
|
||||
---
|
||||
|
||||
## 2. Module Layout
|
||||
|
||||
```
|
||||
apps/nexus/
|
||||
├── bux.toml
|
||||
├── README.md
|
||||
├── public/
|
||||
│ ├── index.html
|
||||
│ └── 404.html
|
||||
└── src/
|
||||
├── Main.bux # Entry point: config, socket setup, spawn workers, run acceptor
|
||||
├── Config.bux # ServerConfig struct + CTFE defaults
|
||||
├── Errors.bux # HttpError enum + Result/Option helpers
|
||||
├── Http.bux # HttpMethod enum, Request/Response structs, status text, MIME
|
||||
├── Parser.bux # @[Checked] HTTP/1.1 request parser
|
||||
├── Router.bux # Route, Router, HttpHandler interface
|
||||
├── Middleware.bux # Middleware interface + LoggingMiddleware
|
||||
├── Handlers.bux # Static files, API, WebSocket upgrade, 404
|
||||
└── Server.bux # ConnectionTask, Worker, acceptor loop, channel plumbing
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Language Features to Exercise
|
||||
|
||||
| Feature | Where |
|
||||
|---------|-------|
|
||||
| Modules & `pub`/`private` | Every file |
|
||||
| Algebraic enums | `HttpMethod`, `HttpError`, `ParseResult` |
|
||||
| Pattern matching (`match`) | Error dispatch in `Main`, method parsing |
|
||||
| Structs + `extend` methods | `HttpRequest`, `HttpResponse`, `Router`, `ServerConfig` |
|
||||
| `interface` + `extend ... for` | `HttpHandler`, `Middleware` |
|
||||
| Generics | `Channel<ConnectionTask>` |
|
||||
| `for ... in` loops | Header iteration, route dispatch (over `Array<HeaderEntry>` and `Array<Route>`) |
|
||||
| `Array<T>` generic container | Header storage, route table |
|
||||
| `@[Checked]` + `&` / `&mut` | `Parser.bux` string buffer access |
|
||||
| `Result` + `?` operator | Parser fallible operations |
|
||||
| `const func` / CTFE | Default config values |
|
||||
| `spawn` + `Channel<T>` | Thread-pool in `Server.bux` |
|
||||
| Raw multi-line / interpolated strings | Response templates, logs |
|
||||
|
||||
---
|
||||
|
||||
## 4. Core Types
|
||||
|
||||
### 4.1 HTTP Types (`Http.bux`)
|
||||
|
||||
```bux
|
||||
pub enum HttpMethod {
|
||||
GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS, UNKNOWN,
|
||||
}
|
||||
|
||||
pub enum HttpError {
|
||||
BadRequest,
|
||||
NotFound,
|
||||
MethodNotAllowed,
|
||||
InternalError(String),
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
```
|
||||
|
||||
### 4.2 Result Types (`Errors.bux`)
|
||||
|
||||
Bux's `Std::Result` only carries `int` in `Ok`, so Nexus defines domain-specific enums:
|
||||
|
||||
```bux
|
||||
pub enum ParseResult {
|
||||
Ok(HttpRequest),
|
||||
Err(HttpError),
|
||||
}
|
||||
|
||||
pub enum FileResult {
|
||||
Ok(String),
|
||||
Err(HttpError),
|
||||
}
|
||||
```
|
||||
|
||||
Helpers:
|
||||
- `ParseResult_IsOk`, `ParseResult_UnwrapOr`
|
||||
- `FileResult_IsOk`, `FileResult_UnwrapOr`
|
||||
|
||||
If the bootstrap `?` operator only recognizes `Result`/`Option` with exact variant names, parser code will fall back to explicit `match`.
|
||||
|
||||
### 4.3 Handler & Middleware Interfaces
|
||||
|
||||
```bux
|
||||
// Router.bux
|
||||
pub interface HttpHandler {
|
||||
func Handle(self: &Self, req: &HttpRequest) -> HttpResponse;
|
||||
}
|
||||
|
||||
pub enum Handler {
|
||||
StaticFile,
|
||||
ApiHealth,
|
||||
ApiInfo,
|
||||
WsUpgrade,
|
||||
NotFound,
|
||||
}
|
||||
|
||||
extend Handler for HttpHandler {
|
||||
func Handle(self: &Handler, req: &HttpRequest) -> HttpResponse {
|
||||
match self {
|
||||
Handler::StaticFile => return ServeStaticFile(req),
|
||||
Handler::ApiHealth => return HandleApiHealth(),
|
||||
Handler::ApiInfo => return HandleApiInfo(),
|
||||
Handler::WsUpgrade => return HandleWebSocketUpgrade(req),
|
||||
Handler::NotFound => return NotFoundResponse(),
|
||||
}
|
||||
return NotFoundResponse();
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Route {
|
||||
method: HttpMethod;
|
||||
path: String;
|
||||
handler: Handler;
|
||||
}
|
||||
|
||||
pub struct Router {
|
||||
routes: Array<Route>;
|
||||
notFound: Handler;
|
||||
}
|
||||
|
||||
// Middleware.bux
|
||||
pub interface Middleware {
|
||||
func Process(self: &Self, req: &HttpRequest, next: &HttpHandler) -> HttpResponse;
|
||||
}
|
||||
```
|
||||
|
||||
Bux interfaces currently support static dispatch through generics, not dynamic `*Handler` pointers. The route table stores the concrete `Handler` enum and dispatches via `match` (showcasing algebraic enums + pattern matching). Middleware uses the interface with generic bounds (`T: HttpHandler`) for static composition.
|
||||
|
||||
---
|
||||
|
||||
## 5. Data Flow
|
||||
|
||||
1. `Main` reads `ServerConfig` and creates a listening socket.
|
||||
2. `Main` spawns `workerCount - 1` worker threads; the main thread becomes the last worker.
|
||||
3. `Main` spawns one dedicated acceptor thread that loops on `Net_Accept` and pushes `ConnectionTask { fd }` into `Channel<ConnectionTask>`.
|
||||
4. Each worker loops:
|
||||
```
|
||||
task = Channel_Recv(&taskQueue)
|
||||
HandleConnection(task.fd, &appHandler)
|
||||
Net_Close(task.fd)
|
||||
```
|
||||
5. `HandleConnection`:
|
||||
- `Net_Recv` raw request
|
||||
- `ParseRequest(raw)` → `ParseResult` (matched explicitly; `?` is used only for `Result`/int payloads)
|
||||
- On success, `Router.Dispatch(&req)` or middleware chain
|
||||
- `Http_BuildResponse(resp)` + `Net_Send`
|
||||
|
||||
---
|
||||
|
||||
## 6. Parser Design (`Parser.bux`)
|
||||
|
||||
Parser functions take the raw request by value (`String` is already a C-string reference) and build an `HttpRequest`:
|
||||
|
||||
```bux
|
||||
pub func ParseRequest(raw: String) -> ParseResult {
|
||||
match FindToken(raw, " ") {
|
||||
ParseResult::Ok(end) => {
|
||||
let methodStr: String = Slice(raw, 0, end);
|
||||
let method: HttpMethod = ParseMethod(methodStr);
|
||||
// ... path, version, headers, body
|
||||
return ParseResult_NewOk(req);
|
||||
}
|
||||
ParseResult::Err(e) => return ParseResult_NewErr(e),
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`@[Checked]` is used where mutable borrows make sense (e.g. `SetResponseCode(resp: &mut HttpResponse, code: int)`), not on raw string parsing.
|
||||
|
||||
Headers are stored in a dynamically grown `Array<HeaderEntry>`.
|
||||
|
||||
---
|
||||
|
||||
## 7. Router & Middleware
|
||||
|
||||
### 7.1 Router Dispatch
|
||||
|
||||
```bux
|
||||
extend Router {
|
||||
pub func Dispatch(self: *Router, req: &HttpRequest) -> HttpResponse {
|
||||
for route in self.routes {
|
||||
if route.method == req.method && String_Eq(route.path, req.path) {
|
||||
return route.handler.Handle(req); // Handler enum implements HttpHandler
|
||||
}
|
||||
}
|
||||
return self.notFound.Handle(req);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 7.2 Middleware Chain
|
||||
|
||||
`LoggingMiddleware` logs method/path and delegates to `next`:
|
||||
|
||||
```bux
|
||||
pub struct LoggingMiddleware {}
|
||||
|
||||
extend LoggingMiddleware for Middleware {
|
||||
func Process<T: HttpHandler>(self: *LoggingMiddleware, req: &HttpRequest, next: &T) -> HttpResponse {
|
||||
Print(Http_MethodName(req.method));
|
||||
Print(" ");
|
||||
PrintLine(req.path);
|
||||
return next.Handle(req);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Chaining is achieved with a generic `MiddlewareHandler<M: Middleware, T: HttpHandler>` struct that implements `HttpHandler` and holds `(middleware, next)`. This uses Bux's static-dispatch interfaces (no vtable pointers).
|
||||
|
||||
---
|
||||
|
||||
## 8. Handlers (`Handlers.bux`)
|
||||
|
||||
| Handler | Purpose |
|
||||
|---------|---------|
|
||||
| `StaticFileHandler` | Serve files from `public/` with MIME sniffing and path-traversal guard |
|
||||
| `HealthHandler` | `GET /api/health` → JSON status |
|
||||
| `InfoHandler` | `GET /api/info` → JSON server info |
|
||||
| `WsUpgradeHandler` | `GET /ws` or `Upgrade: websocket` → 101 response with placeholder accept key |
|
||||
| `NotFoundHandler` | 404 JSON/text fallback |
|
||||
|
||||
All implement `HttpHandler`.
|
||||
|
||||
---
|
||||
|
||||
## 9. Server & Thread Pool (`Server.bux`)
|
||||
|
||||
```bux
|
||||
pub struct ConnectionTask {
|
||||
fd: int;
|
||||
}
|
||||
|
||||
pub struct Server {
|
||||
config: ServerConfig;
|
||||
taskQueue: Channel<ConnectionTask>;
|
||||
handler: *HttpHandler;
|
||||
}
|
||||
```
|
||||
|
||||
Flow:
|
||||
1. `Server_Run(&server)` creates socket, binds, listens.
|
||||
2. Spawns workers; each calls `Worker_Run(&server)`.
|
||||
3. `Acceptor_Run(&server)` loops:
|
||||
```bux
|
||||
let fd: int = Net_Accept(serverFd);
|
||||
if fd >= 0 {
|
||||
let task: ConnectionTask = ConnectionTask { fd: fd };
|
||||
Channel_Send<ConnectionTask>(&server.taskQueue, task);
|
||||
}
|
||||
```
|
||||
4. `Worker_Run` pops tasks and calls `HandleConnection(task.fd, server.handler)`.
|
||||
|
||||
Socket fd ownership is always with the worker that received the task; it is closed before the worker loops again.
|
||||
|
||||
---
|
||||
|
||||
## 10. Config (`Config.bux`)
|
||||
|
||||
```bux
|
||||
pub struct ServerConfig {
|
||||
bindAddr: String;
|
||||
port: int;
|
||||
workerCount: int;
|
||||
publicDir: String;
|
||||
backlog: int;
|
||||
}
|
||||
|
||||
const func DefaultConfig() -> ServerConfig {
|
||||
return ServerConfig {
|
||||
bindAddr: "0.0.0.0",
|
||||
port: 8080,
|
||||
workerCount: 4,
|
||||
publicDir: "public",
|
||||
backlog: 128,
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 11. Testing & Verification
|
||||
|
||||
- `buxc check` must pass on the whole package.
|
||||
- `buxc run` starts the server; verify with:
|
||||
- `curl http://localhost:8080/`
|
||||
- `curl http://localhost:8080/api/health`
|
||||
- `curl http://localhost:8080/api/info`
|
||||
- `curl -i -N -H "Upgrade: websocket" -H "Connection: Upgrade" http://localhost:8080/ws`
|
||||
- Existing integration test surface (`buxc check` on `apps/nexus`) remains the gate.
|
||||
|
||||
---
|
||||
|
||||
## 12. Out of Scope
|
||||
|
||||
- Real HTTP/2 frame parsing or WebSocket frame parsing (keep detection/upgrade only, same as current).
|
||||
- TLS/HTTPS.
|
||||
- Full SHA-1/Base64 WebSocket accept key (keep placeholder).
|
||||
- Generic `Result<T, E>` (Bux `Std::Result` is `Ok(int)` only; use domain enums instead).
|
||||
|
||||
---
|
||||
|
||||
## 13. Risks & Mitigations
|
||||
|
||||
| Risk | Mitigation |
|
||||
|------|------------|
|
||||
| `interface` vtables with generic structs | Avoid generic interfaces; use concrete `*HttpHandler` pointers. |
|
||||
| `Channel<T>` with non-trivial `T` | `ConnectionTask` contains only `int`; safe to copy. |
|
||||
| `?` operator on custom enums | Fall back to explicit `match` if needed. |
|
||||
| `@[Checked]` borrow checker on raw `String` | Use `&String` for read-only parsing; avoid `&mut` unless mutation is required. |
|
||||
|
||||
---
|
||||
|
||||
## 14. Success Criteria
|
||||
|
||||
1. `buxc check apps/nexus` passes with zero errors.
|
||||
2. `buxc run` in `apps/nexus` serves static files and API endpoints correctly.
|
||||
3. The new code uses at minimum: modules, algebraic enums, pattern matching, interfaces, `extend`, generics, `for ... in`, `Channel<T>`, `spawn`, `Result`/`Option`, `@[Checked]`.
|
||||
Reference in New Issue
Block a user