feat: crypto library, Nexus HTTP server, JWT CLI, Boko web framework

- lib/crypto/: 9-module crypto library (Hash, HMAC, Base64/URL, Random, AES, RSA, ECDSA, Ed25519, JWT)
- rt/runtime.c: +346 lines OpenSSL crypto primitives (SHA-1/384/512, HMAC, Base64URL, AES-CBC/GCM, RSA, ECDSA, Ed25519)
- apps/nexus/: multi-threaded HTTP/1.1 + HTTP/2 detect + WebSocket server
- apps/jwt-pitbul/: JWT CLI tool (sign, verify, decode, keygen)
- apps/boko-framework/: FastAPI-inspired async web framework
- test_crypto/: crypto library test suite (23 tests)
This commit is contained in:
2026-06-07 22:15:00 +03:00
parent a2617e9954
commit e7e900973f
47 changed files with 3983 additions and 6 deletions
+177
View File
@@ -0,0 +1,177 @@
# Boko Framework
**Async web framework for Bux — inspired by FastAPI.**
Boko is a lightweight, multi-threaded web framework that brings FastAPI-style routing to the Bux programming language. It handles HTTP parsing, path pattern matching, query parameter extraction, and response building so you can focus on your application logic.
## Quick Start
```bash
cd apps/boko-framework
../../buxc build
./boko-framework
```
Open `http://localhost:8080` — you'll see the demo landing page.
## How It Works
Boko follows a **single-dispatch-function** pattern. You define one function — `Boko_Router` — and the framework calls it for every incoming request:
```bux
import Boko::{Request, Response, Response_Json, Response_Html, Response_NotFound,
Path_Match, Request_GetQuery, Request_GetPathParam,
App, App_New, App_Run};
func Boko_Router(req: Request) -> Response {
// Route: GET /
if String_Eq(req.path, "/") {
return Response_Html("<h1>Hello Boko!</h1>");
}
// Route: GET /api/health
if String_Eq(req.path, "/api/health") {
return Response_Json("{\"status\":\"ok\"}");
}
// Route: GET /users/{id} — path parameter
if Path_Match("/users/{id}", req.path, &req) {
let id: String = Request_GetPathParam(&req, "id");
// Build JSON response with id
return Response_Json(...);
}
// Route: GET /search?q=... — query parameter
if String_Eq(req.path, "/search") {
let q: String = Request_GetQuery(&req, "q");
return Response_Json(...);
}
return Response_NotFound();
}
func Main() -> int {
let app: App = App_New(8080, 4); // port, threads
App_Run(&app);
return 0;
}
```
## API Reference
### Request
| Field | Type | Description |
|-------|------|-------------|
| `method` | `HttpVerb` | GET, POST, PUT, DELETE |
| `path` | `String` | Request path (without query string) |
| `body` | `String` | Request body (for POST/PUT) |
| `headerCount` | `int` | Number of headers |
| Function | Returns | Description |
|----------|---------|-------------|
| `Request_GetHeader(req, name)` | `String` | Get header value by name |
| `Request_GetQuery(req, name)` | `String` | Get query parameter by name |
| `Request_GetPathParam(req, name)` | `String` | Get extracted path parameter |
### Response
| Constructor | Content-Type | Status |
|-------------|-------------|--------|
| `Response_Html(html)` | `text/html` | 200 |
| `Response_Json(json)` | `application/json` | 200 |
| `Response_Text(text)` | `text/plain` | 200 |
| `Response_Redirect(url)` | — | 302 |
| `Response_NotFound()` | `application/json` | 404 |
| `Response_Error(code, msg)` | `application/json` | custom |
| `Response_NoContent()` | — | 204 |
### Path Matching
`Path_Match(pattern, actualPath, req)` matches a pattern with `{param}` placeholders:
```bux
// Pattern: /users/{id}/posts/{postId}
// Actual: /users/42/posts/7
// Extracts: id=42, postId=7
if Path_Match("/users/{id}/posts/{postId}", req.path, &req) {
let userId: String = Request_GetPathParam(&req, "id"); // "42"
let postId: String = Request_GetPathParam(&req, "postId"); // "7"
}
```
The function returns `true` if the pattern matches and populates `req.pathParamKeys` / `req.pathParamValues`.
### App
```bux
let app: App = App_New(8080, 4); // port 8080, 4 worker threads
App_Run(&app); // blocks, handles requests
```
## Architecture
```
Incoming connection
Net_Accept() ─── worker thread (1 of N)
Net_Recv() → raw HTTP bytes
Request_Parse() → method, path, query, headers, body
Boko_Router(req) ←── YOUR CODE
Response_Build() → HTTP/1.1 response string
Net_Send() → bytes to client
Net_Close()
```
## Endpoints in Demo App
| Method | Path | Description |
|--------|------|-------------|
| GET | `/` | Landing page (HTML) |
| GET | `/api/health` | Health check (JSON) |
| GET | `/api/info` | Framework info (JSON) |
| GET | `/hello?name=X` | Query param demo |
| GET | `/users/{id}` | Path param demo |
| GET | `/posts/{id}/comments/{cid}` | Multi path param demo |
| GET | `/redirect` | 302 redirect to `/` |
| POST | `/api/echo` | Echo request body |
## Project Structure
```
apps/boko-framework/
├── bux.toml # Package manifest
├── README.md # This file
└── src/
├── Boko.bux # Framework core (~500 lines)
└── Main.bux # Example app (~150 lines)
```
## Design Philosophy
Boko is intentionally simple. Instead of complex DSLs or code generation, it gives you:
- **One function to write** — `Boko_Router(req) -> Response`
- **Direct control** — you write plain if/else or match for routing
- **No magic** — path params, query params, headers are explicit function calls
- **Fast** — multi-threaded accept loop, zero allocations where possible
This is the same philosophy as Go's `net/http` — simple, explicit, composable.
## License
Part of the Bux project. See root [LICENSE](../../LICENSE).
+1
View File
@@ -0,0 +1 @@
EXIT:137
+8
View File
@@ -0,0 +1,8 @@
[Package]
Name = "boko-framework"
Version = "0.1.0"
Type = "bin"
Description = "Boko — async web framework for Bux, inspired by FastAPI"
[Build]
Output = "Bin"
+1
View File
@@ -0,0 +1 @@
EXIT:137
+1
View File
@@ -0,0 +1 @@
EXIT:143
+1
View File
@@ -0,0 +1 @@
EXIT:137
+2
View File
@@ -0,0 +1,2 @@
bash: ред 1: ../../buxc_lir: Няма такъв файл или директория
EXIT:127
+1
View File
@@ -0,0 +1 @@
EXIT:143
+1
View File
@@ -0,0 +1 @@
EXIT:143
+2
View File
@@ -0,0 +1,2 @@
bux 0.1.0 (bootstrap)
EXIT:0
+40
View File
@@ -0,0 +1,40 @@
nim c -o:buxc -d:release --opt:size bootstrap/main.nim
Hint: used config file '/etc/nim/nim.cfg' [Conf]
Hint: used config file '/etc/nim/config.nims' [Conf]
...........................................................................................................................................
/home/ziko/z-git/bux/bux/bootstrap/lexer.nim(340, 12) Hint: 'check3' is declared but not used [XDeclaredButNotUsed]
/home/ziko/z-git/bux/bux/bootstrap/lexer.nim(350, 12) Hint: 'checkEq' is declared but not used [XDeclaredButNotUsed]
/home/ziko/z-git/bux/bux/bootstrap/lexer.nim(64, 6) Hint: 'match' is declared but not used [XDeclaredButNotUsed]
/home/ziko/z-git/bux/bux/bootstrap/lexer.nim(84, 6) Hint: 'emitWarning' is declared but not used [XDeclaredButNotUsed]
..
/home/ziko/z-git/bux/bux/bootstrap/parser.nim(663, 7) Hint: 'loc' is declared but not used [XDeclaredButNotUsed]
/home/ziko/z-git/bux/bux/bootstrap/parser.nim(1001, 9) Hint: 'isVar' is declared but not used [XDeclaredButNotUsed]
/home/ziko/z-git/bux/bux/bootstrap/parser.nim(59, 6) Hint: 'match' is declared but not used [XDeclaredButNotUsed]
.......
/home/ziko/z-git/bux/bux/bootstrap/sema.nim(1150, 9) Hint: 'operandType' is declared but not used [XDeclaredButNotUsed]
/home/ziko/z-git/bux/bux/bootstrap/sema.nim(1155, 9) Hint: 'operandType' is declared but not used [XDeclaredButNotUsed]
/home/ziko/z-git/bux/bux/bootstrap/sema.nim(1165, 9) Hint: 'subjectType' is declared but not used [XDeclaredButNotUsed]
/home/ziko/z-git/bux/bux/bootstrap/sema.nim(1200, 9) Hint: 'operand' is declared but not used [XDeclaredButNotUsed]
/home/ziko/z-git/bux/bux/bootstrap/sema.nim(94, 6) Hint: 'emitWarning' is declared but not used [XDeclaredButNotUsed]
.
/home/ziko/z-git/bux/bux/bootstrap/manifest.nim(93, 11) Hint: 'val' is declared but not used [XDeclaredButNotUsed]
..
/home/ziko/z-git/bux/bux/bootstrap/hir_lower.nim(1057, 9) Hint: 'loweredIter' is declared but not used [XDeclaredButNotUsed]
.
/home/ziko/z-git/bux/bux/bootstrap/lir.nim(5, 8) Warning: imported and not used: 'types' [UnusedImport]
.
/home/ziko/z-git/bux/bux/bootstrap/lir_lower.nim(419, 9) Hint: 't' is declared but not used [XDeclaredButNotUsed]
/home/ziko/z-git/bux/bux/bootstrap/lir_lower.nim(595, 9) Hint: 'bodyLbl' is declared but not used [XDeclaredButNotUsed]
/home/ziko/z-git/bux/bux/bootstrap/lir_lower.nim(734, 11) Hint: 'exprVal' is declared but not used [XDeclaredButNotUsed]
/home/ziko/z-git/bux/bux/bootstrap/lir_lower.nim(746, 9) Hint: 'exprVal' is declared but not used [XDeclaredButNotUsed]
/home/ziko/z-git/bux/bux/bootstrap/lir_lower.nim(6, 8) Warning: imported and not used: 'ast' [UnusedImport]
.
/home/ziko/z-git/bux/bux/bootstrap/lir_c_backend.nim(20, 6) Hint: 'emit' is declared but not used [XDeclaredButNotUsed]
/home/ziko/z-git/bux/bux/bootstrap/lir_c_backend.nim(46, 6) Hint: 'typeFromValue' is declared but not used [XDeclaredButNotUsed]
/home/ziko/z-git/bux/bux/bootstrap/lir_c_backend.nim(58, 6) Hint: 'setTempType' is declared but not used [XDeclaredButNotUsed]
/home/ziko/z-git/bux/bux/bootstrap/cli.nim(2, 55) Warning: imported and not used: 'lir' [UnusedImport]
Hint: [Link]
Hint: mm: orc; threads: on; opt: size; options: -d:release
76982 lines; 1.552s; 174.824MiB peakmem; proj: /home/ziko/z-git/bux/bux/bootstrap/main.nim; out: /home/ziko/z-git/bux/bux/buxc [SuccessX]
# strip buxc
EXIT:0
+19
View File
@@ -0,0 +1,19 @@
#!/bin/bash
cd /home/ziko/z-git/bux/bux/apps/boko-framework
echo "=== CHECK ==="
../../buxc check 2>&1
echo "CHECK_EXIT=$?"
echo ""
echo "=== BUILD ==="
../../buxc build 2>&1
echo "BUILD_EXIT=$?"
echo ""
echo "=== FILES ==="
find . -type f -newer bux.toml 2>/dev/null | head -30
echo ""
echo "=== BINARY SEARCH ==="
file boko-framework 2>/dev/null || echo "no boko-framework"
ls -la build/ 2>/dev/null || echo "no build dir"
+507
View File
@@ -0,0 +1,507 @@
// =============================================================================
// Boko — Async Web Framework for Bux (inspired by FastAPI)
// =============================================================================
module Boko {
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_Contains};
import Std::Task::{Task_Join, TaskHandle};
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_base64url_encode(data: String, len: int) -> String;
extern func bux_base64url_decode(data: String, len: int, outlen: *int) -> String;
// =============================================================================
// HTTP Methods
// =============================================================================
enum HttpVerb {
GET,
POST,
PUT,
DELETE,
PATCH,
HEAD,
OPTIONS,
}
// =============================================================================
// Request — parsed incoming HTTP request
// =============================================================================
struct Request {
method: HttpVerb,
path: String,
body: String,
headerKeys: *String,
headerValues: *String,
headerCount: int,
queryKeys: *String,
queryValues: *String,
queryCount: int,
pathParamKeys: *String,
pathParamValues: *String,
pathParamCount: int,
}
// =============================================================================
// Response — outgoing HTTP response
// =============================================================================
struct Response {
statusCode: int,
contentType: String,
body: String,
extraHeaders: String,
}
// =============================================================================
// App — server instance
// =============================================================================
struct App {
port: int,
threadCount: int,
serverName: String,
}
// =============================================================================
// Request helpers
// =============================================================================
func Request_GetHeader(req: *Request, name: String) -> String {
var i: int = 0;
while i < req.headerCount {
if bux_strcmp(req.headerKeys[i], name) == 0 {
return req.headerValues[i];
}
i = i + 1;
}
return "";
}
func Request_GetQuery(req: *Request, name: String) -> String {
var i: int = 0;
while i < req.queryCount {
if bux_strcmp(req.queryKeys[i], name) == 0 {
return req.queryValues[i];
}
i = i + 1;
}
return "";
}
func Request_GetPathParam(req: *Request, name: String) -> String {
var i: int = 0;
while i < req.pathParamCount {
if bux_strcmp(req.pathParamKeys[i], name) == 0 {
return req.pathParamValues[i];
}
i = i + 1;
}
return "";
}
// =============================================================================
// Response constructors
// =============================================================================
func Response_New(status: int, contentType: String, body: String) -> Response {
var resp: Response;
resp.statusCode = status;
resp.contentType = contentType;
resp.body = body;
resp.extraHeaders = "";
return resp;
}
func Response_Ok(body: String) -> Response {
return Response_New(200, "text/html; charset=utf-8", body);
}
func Response_Html(html: String) -> Response {
return Response_New(200, "text/html; charset=utf-8", html);
}
func Response_Json(json: String) -> Response {
return Response_New(200, "application/json; charset=utf-8", json);
}
func Response_Text(text: String) -> Response {
return Response_New(200, "text/plain; charset=utf-8", text);
}
func Response_Redirect(url: String) -> Response {
var resp: Response;
resp.statusCode = 302;
resp.contentType = "";
resp.body = "";
let sb: *void = bux_sb_new(256);
bux_sb_append(sb, "Location: ");
bux_sb_append(sb, url);
bux_sb_append(sb, "\r\n");
resp.extraHeaders = bux_sb_build(sb);
bux_sb_free(sb);
return resp;
}
func Response_NotFound() -> Response {
return Response_New(404, "application/json; charset=utf-8",
"{\"error\":\"not_found\"}");
}
func Response_Error(status: int, message: String) -> Response {
return Response_New(status, "application/json; charset=utf-8", message);
}
func Response_NoContent() -> Response {
return Response_New(204, "", "");
}
// =============================================================================
// Build HTTP response string from Response struct
// =============================================================================
func Response_Build(resp: Response) -> String {
let sb: *void = bux_sb_new(2048);
// Status line
bux_sb_append(sb, "HTTP/1.1 ");
bux_sb_append_int(sb, resp.statusCode as int64);
bux_sb_append(sb, " ");
if resp.statusCode == 200 { bux_sb_append(sb, "OK"); }
else if resp.statusCode == 201 { bux_sb_append(sb, "Created"); }
else if resp.statusCode == 204 { bux_sb_append(sb, "No Content"); }
else if resp.statusCode == 302 { bux_sb_append(sb, "Found"); }
else if resp.statusCode == 400 { bux_sb_append(sb, "Bad Request"); }
else if resp.statusCode == 404 { bux_sb_append(sb, "Not Found"); }
else if resp.statusCode == 500 { bux_sb_append(sb, "Internal Server Error"); }
else { bux_sb_append(sb, "OK"); }
bux_sb_append(sb, "\r\n");
// Server
bux_sb_append(sb, "Server: Boko/0.1.0 (Bux)\r\n");
// Extra headers (redirects, CORS, 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 close
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;
}
// =============================================================================
// Parse query string: ?key=val&key2=val2 → key-value arrays
// =============================================================================
func Query_Parse(req: *Request, queryString: String) {
if bux_strlen(queryString) == 0 { return; }
let pairCount: uint = bux_str_split_count(queryString, "&");
let cap: int = pairCount as int + 1;
req.queryKeys = bux_alloc(cap as uint * 8) as *String;
req.queryValues = bux_alloc(cap as uint * 8) as *String;
req.queryCount = 0;
var i: uint = 0;
while i < pairCount {
let pair: String = bux_str_split_part(queryString, "&", i);
let eqPos: String = bux_strstr(pair, "=");
var key: String = pair;
var value: String = "";
if eqPos as uint != 0 {
let keyLen: uint = eqPos as uint - pair as uint;
key = bux_str_slice(pair, 0, keyLen);
let valStart: uint = keyLen + 1;
let pairLen: uint = bux_strlen(pair);
if valStart < pairLen {
value = bux_str_slice(pair, valStart, pairLen - valStart);
}
}
req.queryKeys[req.queryCount] = key;
req.queryValues[req.queryCount] = value;
req.queryCount = req.queryCount + 1;
i = i + 1;
}
}
// =============================================================================
// Parse incoming HTTP request from raw bytes
// =============================================================================
func Request_Parse(raw: String) -> Request {
var req: Request;
req.method = HttpVerb { tag: HttpVerb_GET };
req.path = "/";
req.body = "";
req.headerCount = 0;
req.queryCount = 0;
req.pathParamCount = 0;
if raw as uint == 0 { return req; }
let rawLen: uint = bux_strlen(raw);
if rawLen == 0 { return req; }
// Find header/body boundary
let boundary: String = bux_strstr(raw, "\r\n\r\n");
var headerLen: uint = rawLen;
if boundary as uint != 0 {
headerLen = boundary as uint - raw as uint;
let bodyStart: uint = headerLen + 4;
if bodyStart < rawLen {
req.body = bux_str_slice(raw, bodyStart, rawLen - bodyStart);
}
}
if headerLen == 0 { return req; }
let headerBlock: String = bux_str_slice(raw, 0, headerLen);
let lineCount: uint = bux_str_split_count(headerBlock, "\r\n");
if lineCount == 0 { return req; }
// Parse request line
let requestLine: String = bux_str_split_part(headerBlock, "\r\n", 0);
if bux_strlen(requestLine) > 0 {
let methodStr: String = bux_str_split_part(requestLine, " ", 0);
if String_Eq(methodStr, "GET") { req.method = HttpVerb { tag: HttpVerb_GET }; }
else if String_Eq(methodStr, "POST") { req.method = HttpVerb { tag: HttpVerb_POST }; }
else if String_Eq(methodStr, "PUT") { req.method = HttpVerb { tag: HttpVerb_PUT }; }
else if String_Eq(methodStr, "DELETE") { req.method = HttpVerb { tag: HttpVerb_DELETE }; }
let fullPath: String = bux_str_split_part(requestLine, " ", 1);
// Split path and query string
let qmark: String = bux_strstr(fullPath, "?");
if qmark as uint != 0 {
let pathLen: uint = qmark as uint - fullPath as uint;
req.path = bux_str_slice(fullPath, 0, pathLen);
let qsStart: uint = pathLen + 1;
let fullLen: uint = bux_strlen(fullPath);
if qsStart < fullLen {
let qs: String = bux_str_slice(fullPath, qsStart, fullLen - qsStart);
Query_Parse(&req, qs);
}
} else {
req.path = fullPath;
}
}
// Parse headers
let cap: int = lineCount as int + 1;
req.headerKeys = bux_alloc(cap as uint * 8) as *String;
req.headerValues = bux_alloc(cap as uint * 8) as *String;
var i: uint = 1;
while i < lineCount {
let line: String = bux_str_split_part(headerBlock, "\r\n", i);
let colonPos: String = bux_strstr(line, ": ");
if colonPos as uint != 0 {
let keyLen: uint = colonPos as uint - line as uint;
let key: String = bux_str_slice(line, 0, keyLen);
let valStart: uint = keyLen + 2;
let lineLen: uint = bux_strlen(line);
var value: String = "";
if valStart < lineLen {
value = bux_str_slice(line, valStart, lineLen - valStart);
}
req.headerKeys[req.headerCount] = key;
req.headerValues[req.headerCount] = value;
req.headerCount = req.headerCount + 1;
}
i = i + 1;
}
return req;
}
// =============================================================================
// Path pattern matching: /users/{id} against /users/42 → extracts id=42
// =============================================================================
func Path_Match(pattern: String, path: String, req: *Request) -> bool {
// Split both by "/"
let patParts: uint = bux_str_split_count(pattern, "/");
let pathParts: uint = bux_str_split_count(path, "/");
if patParts != pathParts { return false; }
// Count params to allocate
var paramCount: int = 0;
var pi: uint = 0;
while pi < patParts {
let patPart: String = bux_str_split_part(pattern, "/", pi);
if String_StartsWith(patPart, "{") && String_Contains(patPart, "}") {
paramCount = paramCount + 1;
}
pi = pi + 1;
}
let cap: int = paramCount + 1;
req.pathParamKeys = bux_alloc(cap as uint * 8) as *String;
req.pathParamValues = bux_alloc(cap as uint * 8) as *String;
req.pathParamCount = 0;
// Match and extract
var i: uint = 0;
while i < patParts {
let patPart: String = bux_str_split_part(pattern, "/", i);
let pathPart: String = bux_str_split_part(path, "/", i);
if String_StartsWith(patPart, "{") && String_Contains(patPart, "}") {
// Extract param name from {name}
let braceOpen: uint = 1; // skip "{"
let braceClose: String = bux_strstr(patPart, "}");
var nameLen: uint = 0;
if braceClose as uint != 0 {
nameLen = braceClose as uint - patPart as uint - 1;
}
let name: String = bux_str_slice(patPart, braceOpen, nameLen);
req.pathParamKeys[req.pathParamCount] = name;
req.pathParamValues[req.pathParamCount] = pathPart;
req.pathParamCount = req.pathParamCount + 1;
} else {
// Literal match
if !String_Eq(patPart, pathPart) { return false; }
}
i = i + 1;
}
return true;
}
// =============================================================================
// App constructor
// =============================================================================
func App_New(port: int, threadCount: int) -> App {
var app: App;
app.port = port;
app.threadCount = threadCount;
app.serverName = "Boko/0.1.0 (Bux)";
return app;
}
// =============================================================================
// Forward declaration — user must implement this in their module
// =============================================================================
func Boko_Router(req: Request) -> Response;
// =============================================================================
// Handle a single connection: parse → dispatch → respond
// =============================================================================
func App_HandleConnection(clientFd: int) {
let raw: String = Net_Recv(clientFd, 8192);
if raw as uint == 0 { return; }
if bux_strlen(raw) == 0 { return; }
let req: Request = Request_Parse(raw);
// Call user-defined dispatch (forward declaration — user must implement)
let resp: Response = Boko_Router(req);
// Log
if req.method.tag == HttpVerb_GET { Print("GET "); }
else if req.method.tag == HttpVerb_POST { Print("POST "); }
else if req.method.tag == HttpVerb_PUT { Print("PUT "); }
else if req.method.tag == HttpVerb_DELETE { Print("DELETE "); }
else { Print("? "); }
Print(req.path);
Print(" → ");
PrintInt(resp.statusCode);
PrintLine("");
// Send
let respStr: String = Response_Build(resp);
Net_Send(clientFd, respStr);
}
// =============================================================================
// Worker thread: accept loop
// =============================================================================
func App_Worker(serverFd: int) {
while true {
let clientFd: int = Net_Accept(serverFd);
if clientFd < 0 { continue; }
App_HandleConnection(clientFd);
Net_Close(clientFd);
}
}
// =============================================================================
// App_Run: start the server (blocking)
// =============================================================================
func App_Run(app: *App) {
PrintLine("╔══════════════════════════════════════╗");
PrintLine("║ Boko Framework v0.1.0 ║");
PrintLine("║ Async web framework for Bux ║");
PrintLine("╚══════════════════════════════════════╝");
PrintLine("");
let fd: int = Net_Create();
if fd < 0 {
PrintLine("FATAL: socket() failed");
return;
}
Net_SetReuse(fd);
if !Net_Bind(fd, "0.0.0.0", app.port) {
Print("FATAL: bind(:");
PrintInt(app.port);
Print(") failed: ");
PrintLine(Net_LastError());
Net_Close(fd);
return;
}
if !Net_Listen(fd, 128) {
PrintLine("FATAL: listen() failed");
Net_Close(fd);
return;
}
Print("✓ Boko running on http://0.0.0.0:");
PrintInt(app.port);
PrintLine("");
Print("✓ Workers: ");
PrintInt(app.threadCount);
PrintLine("");
PrintLine("");
// Spawn workers
var i: int = 0;
while i < app.threadCount - 1 {
spawn App_Worker(fd);
i = i + 1;
}
// Main thread is the last worker
App_Worker(fd);
}
} // module Boko
+160
View File
@@ -0,0 +1,160 @@
// =============================================================================
// Boko Example App — demonstrates the framework API
// =============================================================================
module Main {
import Std::Io::{PrintLine, Print};
import Std::String::{String_Eq};
import Boko::{
App, App_New, App_Run,
Request, Response,
Request_GetQuery, Request_GetPathParam,
Response_Html, Response_Json, Response_NotFound, Response_Redirect,
Path_Match,
HttpVerb
};
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_build(sb: *void) -> String;
extern func bux_sb_free(sb: *void);
// =============================================================================
// Boko_Router — user-defined dispatch (called by the framework)
// =============================================================================
func Boko_Router(req: Request) -> Response {
// --- GET / ---
if String_Eq(req.path, "/") && req.method.tag == HttpVerb_GET {
return Response_Html(PageHome());
}
// --- GET /api/health ---
if String_Eq(req.path, "/api/health") {
return Response_Json("{\"status\":\"ok\",\"framework\":\"Boko\",\"version\":\"0.1.0\"}");
}
// --- GET /api/info ---
if String_Eq(req.path, "/api/info") {
return Response_Json("{\"name\":\"Boko\",\"language\":\"Bux\",\"inspiration\":\"FastAPI\",\"features\":[\"routing\",\"path-params\",\"query-params\",\"json\"]}");
}
// --- GET /hello?name=World ---
if String_Eq(req.path, "/hello") {
let name: String = Request_GetQuery(&req, "name");
if bux_strlen(name) == 0 { name = "World"; }
let sb: *void = bux_sb_new(256);
bux_sb_append(sb, "<h1>Hello, ");
bux_sb_append(sb, name);
bux_sb_append(sb, "!</h1>");
let html: String = bux_sb_build(sb);
bux_sb_free(sb);
return Response_Html(html);
}
// --- GET /users/{id} ---
if Path_Match("/users/{id}", req.path, &req) {
let id: String = Request_GetPathParam(&req, "id");
let sb: *void = bux_sb_new(128);
bux_sb_append(sb, "{\"id\":");
bux_sb_append(sb, id);
bux_sb_append(sb, ",\"name\":\"User ");
bux_sb_append(sb, id);
bux_sb_append(sb, "\"}");
let json: String = bux_sb_build(sb);
bux_sb_free(sb);
return Response_Json(json);
}
// --- GET /posts/{postId}/comments/{commentId} ---
if Path_Match("/posts/{postId}/comments/{commentId}", req.path, &req) {
let pid: String = Request_GetPathParam(&req, "postId");
let cid: String = Request_GetPathParam(&req, "commentId");
let sb: *void = bux_sb_new(128);
bux_sb_append(sb, "{\"postId\":");
bux_sb_append(sb, pid);
bux_sb_append(sb, ",\"commentId\":");
bux_sb_append(sb, cid);
bux_sb_append(sb, ",\"text\":\"Great post!\"}");
let json: String = bux_sb_build(sb);
bux_sb_free(sb);
return Response_Json(json);
}
// --- GET /redirect → 302 to / ---
if String_Eq(req.path, "/redirect") {
return Response_Redirect("/");
}
// --- POST /api/echo ---
if String_Eq(req.path, "/api/echo") && req.method.tag == HttpVerb_POST {
let sb: *void = bux_sb_new(256);
bux_sb_append(sb, "{\"echo\":");
bux_sb_append(sb, req.body);
bux_sb_append(sb, "}");
let json: String = bux_sb_build(sb);
bux_sb_free(sb);
return Response_Json(json);
}
// --- 404 ---
return Response_NotFound();
}
// =============================================================================
// HTML landing page (raw multi-line string via backticks)
// =============================================================================
func PageHome() -> String {
return `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Boko Framework</title>
<style>
*{margin:0;padding:0;box-sizing:border-box}
body{font-family:system-ui,sans-serif;background:#0d1117;color:#c9d1d9;min-height:100vh;display:flex;align-items:center;justify-content:center}
.card{background:#161b22;border:1px solid #30363d;border-radius:12px;padding:3rem;max-width:560px;width:100%}
h1{color:#58a6ff;font-size:2rem;margin-bottom:.5rem}
.tag{color:#8b949e;font-size:.9rem;margin-bottom:2rem}
h3{color:#d2a8ff;margin:1.5rem 0 .75rem}
.ep{display:flex;gap:1rem;padding:.35rem 0;font-family:monospace;font-size:.9rem}
.ep .m{color:#3fb950;font-weight:bold;min-width:52px}
.ep .p{color:#c9d1d9}
.ep .d{color:#484f58;margin-left:auto}
a{color:#58a6ff}
</style>
</head>
<body>
<div class="card">
<h1>⚡ Boko</h1>
<p class="tag">Async web framework for Bux — inspired by FastAPI</p>
<h3>Try it</h3>
<div class="ep"><span class="m">GET</span><span class="p"><a href="/hello?name=Bux">/hello?name=Bux</a></span><span class="d">query param</span></div>
<div class="ep"><span class="m">GET</span><span class="p"><a href="/users/42">/users/42</a></span><span class="d">path param</span></div>
<div class="ep"><span class="m">GET</span><span class="p"><a href="/posts/7/comments/3">/posts/7/comments/3</a></span><span class="d">multi params</span></div>
<div class="ep"><span class="m">GET</span><span class="p"><a href="/redirect">/redirect</a></span><span class="d">302 → /</span></div>
<div class="ep"><span class="m">GET</span><span class="p"><a href="/api/health">/api/health</a></span><span class="d">JSON</span></div>
<div class="ep"><span class="m">GET</span><span class="p"><a href="/api/info">/api/info</a></span><span class="d">JSON</span></div>
<h3>Features</h3>
<div class="ep"><span class="m">✓</span><span class="p">Path routing with {params}</span></div>
<div class="ep"><span class="m">✓</span><span class="p">Query parameter extraction</span></div>
<div class="ep"><span class="m">✓</span><span class="p">JSON / HTML / Text responses</span></div>
<div class="ep"><span class="m">✓</span><span class="p">Multi-threaded (configurable)</span></div>
<div class="ep"><span class="m">✓</span><span class="p">Redirects (302)</span></div>
<div class="ep"><span class="m">✓</span><span class="p">POST body access</span></div>
</div>
</body>
</html>`;
}
// =============================================================================
// Main — start the server
// =============================================================================
func Main() -> int {
let app: App = App_New(8080, 4);
App_Run(&app);
return 0;
}
} // module Main
+175
View File
@@ -0,0 +1,175 @@
# jwt-pitbul
**JWT CLI tool for the Bux ecosystem — sign, verify, decode, and key generation.**
A standalone command-line utility for working with JSON Web Tokens (RFC 7519). Built on `Std::Crypto`, backed by OpenSSL.
## Quick Start
```bash
cd apps/jwt-pitbul
../../buxc build
./jwt-pitbul
```
## Commands
### `sign` — Create a JWT
```
jwt-pitbul sign <alg> <key> <claims_json>
```
| Argument | Description |
|----------|-------------|
| `<alg>` | Algorithm: `HS256`, `HS384`, `HS512`, `RS256`, `RS384`, `RS512`, `ES256`, `ES384`, `EdDSA` |
| `<key>` | HMAC secret string, or path to PEM file (RSA/ECDSA), or base64 raw key (Ed25519) |
| `<claims_json>` | JSON payload string (e.g. `{"sub":"123","exp":1735689600}`) |
```bash
# HMAC
jwt-pitbul sign HS256 'my-secret-key' '{"sub":"123","role":"admin"}'
# RSA (private key from file)
jwt-pitbul sign RS256 private.pem '{"sub":"456"}'
# ECDSA
jwt-pitbul sign ES256 ec_private.pem '{"sub":"789"}'
# Ed25519 (base64-encoded 32-byte private key)
jwt-pitbul sign EdDSA 'MC4CAQAwBQYDK2VwBCIEIJ...' '{"sub":"000"}'
```
### `verify` — Verify and decode
```
jwt-pitbul verify <token> <alg> <key>
```
```bash
jwt-pitbul verify eyJhbGciOiJ... HS256 'my-secret-key'
# ✓ Signature valid
#
# Header:
# {"alg":"HS256","typ":"JWT"}
#
# Payload:
# {"sub":"123","role":"admin"}
```
### `decode` — Decode without verification
```
jwt-pitbul decode <token>
```
```bash
jwt-pitbul decode eyJhbGciOiJ...
# Decoded (no verification):
#
# Header:
# {"alg":"HS256","typ":"JWT"}
#
# Payload:
# {"sub":"123"}
#
# Signature (base64url): xxxxxxxxx...
```
### `keygen` — Generate keys
```
jwt-pitbul keygen <type>
```
| Type | Output |
|------|--------|
| `ed25519` | Generates an Ed25519 keypair (prints base64 public/private keys) |
| `rsa` | Prints OpenSSL CLI commands to generate RSA 2048-bit keys |
| `ecdsa` | Prints OpenSSL CLI commands to generate ECDSA P-256 keys |
```bash
jwt-pitbul keygen ed25519
# Ed25519 keypair (base64):
# Public: MCowBQYDK2VwAyEA...
# Private: MC4CAQAwBQYDK2VwBCIEIJ...
jwt-pitbul keygen rsa
# RSA key generation requires OpenSSL CLI:
# openssl genpkey -algorithm RSA -out private.pem -pkeyopt rsa_keygen_bits:2048
# openssl rsa -in private.pem -pubout -out public.pem
```
## Supported Algorithms
| Algorithm | Type | Key Format |
|-----------|------|------------|
| HS256 | HMAC-SHA256 | Raw secret string |
| HS384 | HMAC-SHA384 | Raw secret string |
| HS512 | HMAC-SHA512 | Raw secret string |
| RS256 | RSA PKCS#1 v1.5 SHA-256 | PEM file path |
| RS384 | RSA PKCS#1 v1.5 SHA-384 | PEM file path |
| RS512 | RSA PKCS#1 v1.5 SHA-512 | PEM file path |
| ES256 | ECDSA P-256 | PEM file path |
| ES384 | ECDSA P-384 | PEM file path |
| EdDSA | Ed25519 | Base64 32-byte raw key |
## Generating Keys with OpenSSL
### RSA 2048-bit
```bash
openssl genpkey -algorithm RSA -out private.pem -pkeyopt rsa_keygen_bits:2048
openssl rsa -in private.pem -pubout -out public.pem
```
### ECDSA P-256
```bash
openssl ecparam -genkey -name prime256v1 -noout -out ec_private.pem
openssl ec -in ec_private.pem -pubout -out ec_public.pem
```
### ECDSA P-384
```bash
openssl ecparam -genkey -name secp384r1 -noout -out ec_private.pem
openssl ec -in ec_private.pem -pubout -out ec_public.pem
```
### Ed25519
```bash
# In-app generation:
jwt-pitbul keygen ed25519
# Or via OpenSSL:
openssl genpkey -algorithm Ed25519 -out ed25519_private.pem
openssl pkey -in ed25519_private.pem -pubout -out ed25519_public.pem
```
## Error Codes
| Exit | Meaning |
|------|---------|
| 0 | Success |
| 1 | Error (invalid arguments, bad signature, missing file, etc.) |
## Building
```bash
cd apps/jwt-pitbul
../../buxc build # Bootstrap compiler (Nim)
# or
../../buxc_lir build # Self-hosted compiler
```
## Dependencies
- Bux standard library (`Std::Crypto`, `Std::Io`, `Std::String`)
- OpenSSL 1.1.1+ (linked via runtime)
- Bux compiler (`buxc` or `buxc_lir`)
## License
Part of the Bux project. See root [LICENSE](../../LICENSE).
+8
View File
@@ -0,0 +1,8 @@
[Package]
Name = "jwt-pitbul"
Version = "0.1.0"
Type = "bin"
Description = "JWT CLI tool — encode, decode, verify, and key generation"
[Build]
Output = "Bin"
+326
View File
@@ -0,0 +1,326 @@
// =============================================================================
// jwt-pitbul — JWT CLI Tool (encode, decode, verify, keygen)
// Built with Bux + Std::Crypto
// =============================================================================
module Main {
import Std::Io::{PrintLine, Print, PrintInt};
import Std::String::{String_Len, String_Eq, String_Contains};
import Std::Crypto::Jwt::{
JwtAlg,
Jwt_MakeHeader,
Jwt_Encode,
Jwt_Decode,
Jwt_EncodeHS256, Jwt_EncodeHS384, Jwt_EncodeHS512,
Jwt_EncodeRS256, Jwt_EncodeES256, Jwt_EncodeEdDSA
};
import Std::Crypto::Base64::{Base64URL_Decode, Base64_Encode};
import Std::Crypto::Ed25519::{Ed25519_Keypair};
extern func bux_argc() -> int;
extern func bux_argv(index: int) -> String;
extern func bux_alloc(size: uint) -> *void;
extern func bux_read_file(path: String) -> String;
extern func bux_file_exists(path: String) -> int;
extern func bux_str_split_part(s: String, delim: String, index: uint) -> String;
extern func bux_str_split_count(s: String, delim: String) -> uint;
extern func bux_base64_encode(data: String, len: int) -> String;
// =============================================================================
// Help / Usage
// =============================================================================
func PrintUsage() {
PrintLine("╔══════════════════════════════════════════════════════╗");
PrintLine("║ jwt-pitbul — JWT CLI Tool v0.1.0 ║");
PrintLine("║ Sign, verify, decode JSON Web Tokens ║");
PrintLine("╚══════════════════════════════════════════════════════╝");
PrintLine("");
PrintLine("Usage:");
PrintLine(" jwt-pitbul sign <alg> <key> <claims>");
PrintLine(" jwt-pitbul verify <token> <alg> <key>");
PrintLine(" jwt-pitbul decode <token>");
PrintLine(" jwt-pitbul keygen <type>");
PrintLine("");
PrintLine("Commands:");
PrintLine(" sign Create a signed JWT from claims JSON");
PrintLine(" verify Verify a JWT signature and print payload");
PrintLine(" decode Decode a JWT without verification");
PrintLine(" keygen Generate cryptographic keys");
PrintLine("");
PrintLine("Algorithms:");
PrintLine(" HS256, HS384, HS512 — HMAC (symmetric)");
PrintLine(" RS256, RS384, RS512 — RSA PKCS#1 v1.5");
PrintLine(" ES256, ES384 — ECDSA P-256 / P-384");
PrintLine(" EdDSA — Ed25519");
PrintLine("");
PrintLine("Key formats:");
PrintLine(" HMAC: raw secret string");
PrintLine(" RSA: path to PEM private/public key file");
PrintLine(" ECDSA: path to PEM private/public key file");
PrintLine(" EdDSA: base64-encoded 32-byte raw key");
PrintLine("");
PrintLine("Key generation:");
PrintLine(" jwt-pitbul keygen rsa # RSA 2048-bit (PEM)");
PrintLine(" jwt-pitbul keygen ecdsa # ECDSA P-256 (PEM)");
PrintLine(" jwt-pitbul keygen ed25519 # Ed25519 (raw base64)");
PrintLine("");
PrintLine("Examples:");
PrintLine(" jwt-pitbul sign HS256 'my-secret' '{\"sub\":\"123\"}'");
PrintLine(" jwt-pitbul verify eyJh... HS256 'my-secret'");
PrintLine(" jwt-pitbul decode eyJh...");
PrintLine(" jwt-pitbul keygen ed25519");
}
// =============================================================================
// Algorithm name → JwtAlg enum
// =============================================================================
func ParseAlg(name: String) -> JwtAlg {
if String_Eq(name, "HS256") { return JwtAlg { tag: JwtAlg_HS256 }; }
if String_Eq(name, "HS384") { return JwtAlg { tag: JwtAlg_HS384 }; }
if String_Eq(name, "HS512") { return JwtAlg { tag: JwtAlg_HS512 }; }
if String_Eq(name, "RS256") { return JwtAlg { tag: JwtAlg_RS256 }; }
if String_Eq(name, "RS384") { return JwtAlg { tag: JwtAlg_RS384 }; }
if String_Eq(name, "RS512") { return JwtAlg { tag: JwtAlg_RS512 }; }
if String_Eq(name, "ES256") { return JwtAlg { tag: JwtAlg_ES256 }; }
if String_Eq(name, "ES384") { return JwtAlg { tag: JwtAlg_ES384 }; }
if String_Eq(name, "EdDSA") { return JwtAlg { tag: JwtAlg_EdDSA }; }
return JwtAlg { tag: JwtAlg_HS256 };
}
// =============================================================================
// Resolve key — for RSA/ECDSA, read PEM file; for HMAC/EdDSA, pass through
// =============================================================================
func ResolveKey(alg: JwtAlg, keyArg: String) -> String {
let algTag: int = alg.tag;
// RSA and ECDSA: key is a path to PEM file
if algTag == JwtAlg_RS256 || algTag == JwtAlg_RS384 || algTag == JwtAlg_RS512 ||
algTag == JwtAlg_ES256 || algTag == JwtAlg_ES384 {
if bux_file_exists(keyArg) != 0 {
let pem: String = bux_read_file(keyArg);
if pem as uint != 0 && String_Len(pem) > 0 {
return pem;
}
Print("ERROR: could not read PEM file: ");
PrintLine(keyArg);
return "";
}
Print("ERROR: PEM file not found: ");
PrintLine(keyArg);
return "";
}
// HMAC and EdDSA: key is used directly
return keyArg;
}
// =============================================================================
// Check if algorithm is symmetric (HMAC) or asymmetric
// =============================================================================
func IsSymmetric(alg: JwtAlg) -> bool {
let t: int = alg.tag;
return t == JwtAlg_HS256 || t == JwtAlg_HS384 || t == JwtAlg_HS512;
}
// =============================================================================
// Command: sign
// =============================================================================
func CmdSign(algName: String, keyArg: String, claimsJson: String) -> int {
let alg: JwtAlg = ParseAlg(algName);
let key: String = ResolveKey(alg, keyArg);
if String_Len(key) == 0 {
return 1;
}
let header: String = Jwt_MakeHeader(alg);
let token: String = Jwt_Encode(header, claimsJson, alg, key);
if String_Len(token) == 0 {
PrintLine("ERROR: signing failed");
return 1;
}
PrintLine(token);
return 0;
}
// =============================================================================
// Command: verify
// =============================================================================
func CmdVerify(token: String, algName: String, keyArg: String) -> int {
let alg: JwtAlg = ParseAlg(algName);
let key: String = ResolveKey(alg, keyArg);
if String_Len(key) == 0 {
return 1;
}
var header: String = "";
var payload: String = "";
let ok: bool = Jwt_Decode(token, alg, key, &header, &payload);
if ok {
PrintLine("✓ Signature valid");
PrintLine("");
PrintLine("Header:");
PrintLine(header);
PrintLine("");
PrintLine("Payload:");
PrintLine(payload);
return 0;
}
PrintLine("✗ Signature INVALID (or malformed token)");
return 1;
}
// =============================================================================
// Command: decode (no verification)
// =============================================================================
func CmdDecode(token: String) -> int {
let partCount: uint = bux_str_split_count(token, ".");
if partCount != 3 {
PrintLine("ERROR: not a valid JWT (expected 3 parts)");
return 1;
}
let headerB64: String = bux_str_split_part(token, ".", 0);
let payloadB64: String = bux_str_split_part(token, ".", 1);
let sigB64: String = bux_str_split_part(token, ".", 2);
let headerJson: String = Base64URL_Decode(headerB64);
let payloadJson: String = Base64URL_Decode(payloadB64);
PrintLine("Decoded (no verification):");
PrintLine("");
PrintLine("Header:");
PrintLine(headerJson);
PrintLine("");
PrintLine("Payload:");
PrintLine(payloadJson);
PrintLine("");
Print("Signature (base64url): ");
PrintLine(sigB64);
return 0;
}
// =============================================================================
// Command: keygen
// =============================================================================
func CmdKeygen(keyType: String) -> int {
if String_Eq(keyType, "ed25519") {
let pub: *void = bux_alloc(32);
let priv: *void = bux_alloc(32);
if !Ed25519_Keypair(pub, priv) {
PrintLine("ERROR: Ed25519 key generation failed (OpenSSL 1.1.1+ required)");
return 1;
}
let pubB64: String = bux_base64_encode(pub as String, 32);
let privB64: String = bux_base64_encode(priv as String, 32);
PrintLine("Ed25519 keypair (base64):");
Print(" Public: ");
PrintLine(pubB64);
Print(" Private: ");
PrintLine(privB64);
return 0;
}
if String_Eq(keyType, "rsa") {
PrintLine("RSA key generation requires OpenSSL CLI:");
PrintLine(" openssl genpkey -algorithm RSA -out private.pem -pkeyopt rsa_keygen_bits:2048");
PrintLine(" openssl rsa -in private.pem -pubout -out public.pem");
PrintLine("");
PrintLine("(Key generation from within Bux requires PEM write support — coming soon)");
return 0;
}
if String_Eq(keyType, "ecdsa") {
PrintLine("ECDSA key generation requires OpenSSL CLI:");
PrintLine(" openssl ecparam -genkey -name prime256v1 -noout -out ec_private.pem");
PrintLine(" openssl ec -in ec_private.pem -pubout -out ec_public.pem");
PrintLine("");
PrintLine("(Key generation from within Bux requires PEM write support — coming soon)");
return 0;
}
Print("ERROR: unknown key type '");
Print(keyType);
PrintLine("'. Use: rsa, ecdsa, ed25519");
return 1;
}
// =============================================================================
// Main Entry Point
// =============================================================================
func Main() -> int {
let argc: int = bux_argc();
if argc < 2 {
PrintUsage();
return 0;
}
// Collect arguments
let args: *String = bux_alloc(argc as uint * 8) as *String;
var i: int = 0;
while i < argc {
args[i] = bux_argv(i);
i = i + 1;
}
let command: String = args[1];
if String_Eq(command, "help") || String_Eq(command, "--help") || String_Eq(command, "-h") {
PrintUsage();
return 0;
}
// --- sign <alg> <key> <claims> ---
if String_Eq(command, "sign") {
if argc < 5 {
PrintLine("ERROR: 'sign' requires: <alg> <key> <claims_json>");
PrintLine(" jwt-pitbul sign HS256 'secret' '{\"sub\":\"123\"}'");
return 1;
}
return CmdSign(args[2], args[3], args[4]);
}
// --- verify <token> <alg> <key> ---
if String_Eq(command, "verify") {
if argc < 5 {
PrintLine("ERROR: 'verify' requires: <token> <alg> <key>");
PrintLine(" jwt-pitbul verify eyJh... HS256 'secret'");
return 1;
}
return CmdVerify(args[2], args[3], args[4]);
}
// --- decode <token> ---
if String_Eq(command, "decode") {
if argc < 3 {
PrintLine("ERROR: 'decode' requires: <token>");
PrintLine(" jwt-pitbul decode eyJh...");
return 1;
}
return CmdDecode(args[2]);
}
// --- keygen <type> ---
if String_Eq(command, "keygen") {
if argc < 3 {
PrintLine("ERROR: 'keygen' requires: <type>");
PrintLine(" jwt-pitbul keygen ed25519");
return 1;
}
return CmdKeygen(args[3]);
}
Print("ERROR: unknown command '");
Print(command);
PrintLine("'");
PrintLine("Run 'jwt-pitbul help' for usage.");
return 1;
}
} // module Main
+189
View File
@@ -0,0 +1,189 @@
# Nexus
**High-performance, multi-threaded HTTP/1.1, HTTP/2 & WebSocket server — built with the [Bux](https://github.com/bux-lang/bux) programming language.**
Nexus is a from-scratch web server that demonstrates Bux's systems-programming capabilities: raw TCP sockets, pthread-based concurrency, manual memory management, and zero-dependency C ABI interop — all from a clean, modern syntax.
---
## Features
| Area | What's Implemented |
|------|-------------------|
| **HTTP/1.1** | Full request parsing (method, path, headers, body), response building with status codes, content negotiation |
| **Multi-threaded** | Configurable worker pool using the multi-accept pattern — each worker calls `accept()` directly on the shared listen socket |
| **HTTP/2** | Connection preface detection (`PRI * HTTP/2.0`), upgrade-aware routing |
| **WebSocket** | RFC 6455 upgrade handshake detection, `Sec-WebSocket-Key` extraction |
| **Static files** | Serves from `public/` with MIME-type detection for 20+ file types, directory-traversal protection |
| **JSON API** | Built-in `/api/health` and `/api/info` endpoints |
| **Logging** | Per-request structured logging (method, path, status code) |
## Quick Start
```bash
# From the project root
cd apps/nexus
# Build with the bootstrap compiler (Nim)
../../buxc build
# Or with the self-hosted compiler
../../buxc_lir build
# Run
./nexus
```
Server starts on `http://0.0.0.0:8080`:
```
╔══════════════════════════════════════════════╗
║ Nexus HTTP Server v0.1.0 ║
║ High-performance multi-threaded HTTP/1.1 ║
║ HTTP/2 & WebSocket detection included ║
║ Built with Bux ║
╚══════════════════════════════════════════════╝
✓ Server listening on http://0.0.0.0:8080
✓ Worker threads: 4
✓ Static files: ./public/
Endpoints:
GET / — Static files (public/)
GET /api/health — Health check (JSON)
GET /api/info — Server info (JSON)
GET /ws — WebSocket upgrade
ANY /* — Static file serving
```
## Testing It
```bash
# Home page (HTML)
curl http://localhost:8080/
# Health check
curl http://localhost:8080/api/health
# → {"status":"ok","server":"Nexus","version":"0.1.0"}
# Server info
curl http://localhost:8080/api/info
# → {"name":"Nexus","language":"Bux","features":[...]}
# Static file (404 page)
curl http://localhost:8080/404.html
# Nonexistent file
curl http://localhost:8080/nope
# → 404 Not Found
# WebSocket upgrade attempt
curl -H "Upgrade: websocket" \
-H "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==" \
http://localhost:8080/ws
# → 101 Switching Protocols
```
## Architecture
### Thread Model — Multi-Accept
Nexus uses the **multi-accept** pattern rather than a traditional thread pool with a work queue:
```
Main Thread Worker 1 Worker 2 Worker 3
│ │ │ │
├─ socket()/bind()/listen() │ │ │
├─ spawn Worker() ──────────► │ │
├─ spawn Worker() ─────────────────────────► │
├─ spawn Worker() ────────────────────────────────────────►
│ │ │ │
▼ Worker() ▼ accept() ▼ accept() ▼ accept()
accept() loop │ │ │
```
Each worker thread calls `accept()` on the **same** listening socket. The Linux kernel distributes incoming connections across the blocked accept calls — the same mechanism used by nginx and Apache prefork. No mutex, no queue, no context switching between a dispatcher and workers.
### Request Lifecycle
```
Client connects
Net_Accept() → client fd
Net_Recv(fd, 8192) → raw bytes
ParseRequest()
├─ Split headers on \r\n\r\n
├─ Parse request line → method, path, version
└─ Parse header lines → key-value array
Router_Dispatch()
├─ /api/* → JSON handlers
├─ /ws → WebSocket upgrade
├─ Upgrade header → HTTP/2 or WS detection
└─ /* → Static file serving
BuildResponse() → HTTP/1.1 status line + headers + body
Net_Send(fd, response) → bytes to client
Net_Close(fd)
```
### Design Decisions
- **No keep-alive by default.** Each connection is closed after one response. This avoids blocking worker threads on idle clients (Bux doesn't yet expose `SO_RCVTIMEO`).
- **Linear header array instead of hash map.** HTTP requests typically carry 515 headers. A linear scan over a key-value array is faster than hashing for this N, and avoids the complexity of iterating over Bux's generic `StringMap`.
- **Raw extern calls for the string builder.** The stdlib `StringBuilder` wrapper adds a struct indirection. Calling `bux_sb_new` / `bux_sb_append` / `bux_sb_build` directly is simpler and equally safe.
- **WebSocket accept key is a placeholder.** A full implementation needs SHA-1 hashing and Base64 encoding. These aren't in Bux's stdlib yet; they can be added as extern C functions when needed.
## Project Structure
```
apps/nexus/
├── bux.toml # Package manifest
├── README.md # This file
├── src/
│ └── Main.bux # The entire server (~640 lines)
└── public/
├── index.html # Landing page
└── 404.html # Error page
```
Everything lives in one file (`src/Main.bux`) by design — it keeps the module graph flat and the build fast. As the server grows, the HTTP parser, router, and handlers can be split into separate modules.
## Configuration
Edit the constants at the top of `src/Main.bux`:
```bux
const SERVER_PORT: int = 8080; // Listen port
const THREAD_COUNT: int = 4; // Number of worker threads
const RECV_BUF_SIZE: int = 8192; // Receive buffer per request
const SERVER_NAME: String = "Nexus/0.1.0 (Bux)";
const PUBLIC_DIR: String = "public"; // Static files directory
```
## Roadmap
- [ ] Keep-alive with configurable socket timeout
- [ ] Full WebSocket frame read/write (requires SHA-1 + Base64)
- [ ] HTTP/2 binary framing layer (HPACK, stream multiplexing)
- [ ] SSL/TLS via OpenSSL extern bindings
- [ ] Middleware / filter chain
- [ ] Request body parsing (JSON, form-encoded, multipart)
- [ ] Virtual hosts
- [ ] Access logging to file
- [ ] Rate limiting
## License
Nexus is part of the Bux project. See the root [LICENSE](../../LICENSE) for terms.
+1
View File
@@ -0,0 +1 @@
EXIT:137
+1
View File
@@ -0,0 +1 @@
EXIT:124
+1
View File
@@ -0,0 +1 @@
EXIT:124
+1
View File
@@ -0,0 +1 @@
EXIT:124
+1
View File
@@ -0,0 +1 @@
EXIT:124
+2
View File
@@ -0,0 +1,2 @@
timeout: командата „„../../buxc““ не може да бъде изпълнена: Няма такъв файл или директория
EXIT:127
+1
View File
@@ -0,0 +1 @@
EXIT:137
+8
View File
@@ -0,0 +1,8 @@
[Package]
Name = "nexus"
Version = "0.1.0"
Type = "bin"
Description = "High-performance multi-threaded HTTP/1.1, HTTP/2 and WebSocket server"
[Build]
Output = "Bin"
+31
View File
@@ -0,0 +1,31 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>404 — Not Found | Nexus</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;
}
h1 { font-size: 4rem; color: #f85149; margin-bottom: 0.5rem; }
p { color: #8b949e; font-size: 1.2rem; }
.back { margin-top: 2rem; }
.back a { color: #58a6ff; text-decoration: none; }
.back a:hover { text-decoration: underline; }
</style>
</head>
<body>
<h1>404</h1>
<p>The requested resource was not found on this server.</p>
<p class="back"><a href="/">← Back to Home</a></p>
</body>
</html>
+129
View File
@@ -0,0 +1,129 @@
<!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>
+550
View File
@@ -0,0 +1,550 @@
// =============================================================================
// 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};
import Std::Task::{Task_Join, TaskHandle};
// =============================================================================
// 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;
// =============================================================================
// HTTP Method Enum (simple enum — no data payload)
// =============================================================================
enum HttpMethod {
GET,
POST,
PUT,
DELETE,
PATCH,
HEAD,
OPTIONS,
UNKNOWN,
}
// =============================================================================
// 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 raw as uint == 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 boundary as uint != 0 {
headerLen = boundary as uint - raw as uint;
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, "") || req.path as uint == 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 colonPos as uint != 0 {
let keyLen: uint = colonPos as uint - line as uint;
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 colonPos2 as uint != 0 {
let keyLen: uint = colonPos2 as uint - line as uint;
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_char(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 content as uint == 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 raw as uint == 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("");
// 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;
}
} // module Main
+2
View File
@@ -0,0 +1,2 @@
bux 0.1.0 (bootstrap)
EXIT:0