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:
@@ -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).
|
||||
Reference in New Issue
Block a user