Files
bux-lang/docs/superpowers/specs/2026-06-14-nexus-rewrite-design.md
dimgigov aaeb01e518 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
2026-06-15 00:54:03 +03:00

366 lines
11 KiB
Markdown

# 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]`.