Architecture
axon-web Architecture
Section titled “axon-web Architecture”This document describes the planned architecture for the axon-web HTTP framework. It covers the design decisions made before implementation begins so they can be reviewed and challenged.
Status: Architecture defined, implementation not yet started (M14 not begun).
Table of Contents
Section titled “Table of Contents”- High-Level Architecture
- HTTP Router
- Middleware Pipeline
- JSON Module
- WebSocket
- Static File Serving
- HTML Templating
- Performance Design
- File Organization
- CI Infrastructure
1. High-Level Architecture
Section titled “1. High-Level Architecture”axon-web as an Axon Library
Section titled “axon-web as an Axon Library”axon-web is an Axon library, not an application. Users import axon.web and build their own HTTP applications:
;; User's application(import axon.web)(import axon.web.router)(import axon.web.middleware)
(fn handle_home (ctx) (web_response ctx 200 (web_html "<h1>Hello</h1>")))
(fn handle_about (ctx) (web_response ctx 200 (web_html "<h1>About</h1>")))
(fn main () i32 (block (web_server (router (get "/" handle_home) (get "/about" handle_about)) (middleware_chain (middleware.logger) (middleware.cors)))))Request/Response Lifecycle
Section titled “Request/Response Lifecycle”raw bytes → zero-copy parser → middleware chain → router → handler → middleware chain → responseThe parser extracts the request line and headers directly from the socket buffer (no String allocation). The middleware chain is a linked list; each middleware calls next() to pass control. The router matches the method+path and dispatches to the handler.
Dual Mode: HTTP/1.1 and HTTP/2
Section titled “Dual Mode: HTTP/1.1 and HTTP/2”Both HTTP/1.1 and HTTP/2 are served over the same API. HTTP/2 uses the same middleware/router/handler model, with HPACK header compression and stream multiplexing handled beneath the handler layer.
Relationship to axon-lang
Section titled “Relationship to axon-lang”axon-web uses axon-std modules:
mem— memory operations, buffer manipulationstring— string parsing and buildingnet— socket I/O (once available in axon-std)
2. HTTP Router
Section titled “2. HTTP Router”Radix Tree Implementation
Section titled “Radix Tree Implementation”Routes are stored in a radix tree (compressed trie) for O(k) matching where k = path length (not number of routes):
;; Tree structure for: GET /users/:id, GET /users/:id/profile, GET /files/*path(root) └── /users/ └── /:id ├── (match GET) → handle_get_user └── /profile └── (match GET) → handle_get_user_profile └── /files/ └── /*path └── (match *) → handle_staticRoute Matching
Section titled “Route Matching”| Pattern | Example | Matches |
|---|---|---|
| Static | /about | Exactly /about |
| Parameterized | /users/:id/profile | /users/123/profile |
| Wildcard | /files/*path | /files/docs/readme.txt |
Method Dispatch
Section titled “Method Dispatch”Each route node stores handlers by HTTP method:
(struct RouteNode (children (map str RouteNode)) ;; path segment → child node (param_child (option RouteNode)) ;; :param child (wildcard_child (option RouteNode)) ;; *path child (handlers (map method Handler))) ;; GET/POST/PUT/DELETE → handlerBuilt-in 404 (no match) and 405 (method not allowed) handlers return appropriate responses.
3. Middleware Pipeline
Section titled “3. Middleware Pipeline”Signature
Section titled “Signature”(fn middleware (ctx: WebContext, next: fn() -> Response) -> Response)Middleware receives ctx (containing request/response state) and next (the next middleware or handler). It can:
- Read/write
ctx.request(headers, body, path params) - Read/write
ctx.response(headers, status, body) - Short-circuit by returning early (e.g., static file middleware finds the file and returns)
- Call
next()to pass control downstream
Composition
Section titled “Composition”Middleware is a linked list. middleware_chain([A, B, C], handler) produces:
A → B → C → handler ↓ C returns ↓ B returns↓A returnsMiddleware Order
Section titled “Middleware Order”incoming: logger → cors → auth → router → handleroutgoing: handler → auth → cors → loggerBuilt-in Middleware
Section titled “Built-in Middleware”| Middleware | Purpose | Short-circuits? |
|---|---|---|
logger | Logs request method, path, status, duration | No |
cors | Adds CORS headers (Access-Control-*) | No |
auth | Validates auth token, sets ctx.user | Yes (401) |
static_files | Serves files from root directory | Yes (file or 404) |
websocket | Upgrades connection to WebSocket | Yes (if upgrade header) |
Context Modification
Section titled “Context Modification”;; Middleware can read request headers(let (auth_header (web_req_header ctx "Authorization")) (if (string.starts_with auth_header "Bearer ") (ctx_set ctx "auth.token" (string.slice auth_header 7))))
;; Middleware can set response headers(web_set_header ctx "X-Custom-Header" "value")
;; Middleware can short-circuit(if (not (auth.valid ctx)) (return (web_response ctx 401 (web_json "{\"error\":\"unauthorized\"}"))))4. JSON Module
Section titled “4. JSON Module”;; Encode an Axon struct to JSON string(json.encode value: T) -> String
;; Decode a JSON string to an Axon struct(json.decode data: String, type: T) -> Result(T, JsonError)Zero-Allocation Parsing
Section titled “Zero-Allocation Parsing”JSON decoding parses directly from the request buffer — no intermediate String allocation:
;; Parse JSON body directly from the socket buffer(let (buf (web_req_body_buffer ctx)) (match (json.decode buf User) (ok user) (handle_user ctx user) (err e) (web_response ctx 400 (web_json "{\"error\":\"invalid json\"}"))))Error Handling
Section titled “Error Handling”(enum JsonError (syntax_error i32) ;; byte offset of error (type_mismatch str) ;; expected type name (missing_field str)) ;; field name5. WebSocket
Section titled “5. WebSocket”Protocol Upgrade
Section titled “Protocol Upgrade”WebSocket starts as an HTTP request with upgrade headers:
GET /ws HTTP/1.1Connection: UpgradeUpgrade: websocketSec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==The websocket middleware detects these headers, performs the handshake, and upgrades the connection.
Frame Handling
Section titled “Frame Handling”| Frame type | Axon representation |
|---|---|
| Text | (text String) |
| Binary | (binary (slice u8)) |
| Ping | (ping) |
| Pong | (pong) |
| Close | (close u16 String) |
Message Dispatch
Section titled “Message Dispatch”Messages are buffered in full before delivery to the handler (not streaming). This simplifies the handler API at the cost of memory for large messages.
Integration with Middleware
Section titled “Integration with Middleware”WebSocket upgrade goes through the middleware chain — logger, cors, auth all apply before the WebSocket upgrade occurs.
6. Static File Serving
Section titled “6. Static File Serving”File Lookup
Section titled “File Lookup”Files are looked up relative to a configured root directory:
;; If root = "/var/www" and path = "/images/logo.png";; → serve /var/www/images/logo.pngPath traversal attacks (../) are blocked by normalizing the path before lookup.
MIME Types
Section titled “MIME Types”Extension-based MIME type mapping:
(enum MimeType (text_html) ;; .html (text_css) ;; .css (application_js) ;; .js (image_png) ;; .png (image_jpeg) ;; .jpg, .jpeg (application_octet_stream)) ;; fallbackCaching
Section titled “Caching”Conditional requests using ETag and Last-Modified:
;; Client sends:;; If-None-Match: "abc123";; Server responds:;; 304 Not Modified (if ETag matches);; 200 OK + body (if ETag doesn't match);; ETag: "abc123";; Last-Modified: Wed, 21 Oct 2015 12:00:00 GMTDirectory Listing
Section titled “Directory Listing”Directory listing is not supported — it is a security risk.
7. HTML Templating
Section titled “7. HTML Templating”Template Syntax
Section titled “Template Syntax”{{user.name}} <!-- variable substitution -->{{#if user.admin}} <!-- conditional --> <admin-panel />{{/if}}{{#each items as item}} <!-- iteration --> <li>{{item}}</li>{{/each}}Compiled Templates
Section titled “Compiled Templates”Templates are compiled to Axon code at build time, not parsed at runtime:
;; Compiled template produces:(fn render_user_profile (user: User, items: (list Item)) String (let (sb (string.builder))) (string.append sb "<h1>") (string.append sb (str.escape_html user.name)) (string.append sb "</h1>") (if user.admin (string.append sb "<admin-panel />")) (for item items (string.append sb "<li>") (string.append sb (str.escape_html item)) (string.append sb "</li>")) (string.to_string sb)))No Runtime Reflection
Section titled “No Runtime Reflection”Templates are compiled once, called many times with different data. No reflection at runtime — the type is known at compile time.
8. Performance Design
Section titled “8. Performance Design”Zero-Copy Parsing
Section titled “Zero-Copy Parsing”Request line and headers are parsed directly from the socket buffer, never copied to an intermediate String:
;; Parse "GET /path HTTP/1.1\r\n" directly from buffer(let (method (buf.read_until buf ' ') (path (buf.read_until buf ' ')) (version (buf.read_until buf '\r'))) ;; method, path, version are slices into the buffer, not copiesSIMD Header Scanning
Section titled “SIMD Header Scanning”CRLF detection (header delimiter) uses axon-std SIMD intrinsics for fast scanning of large buffers:
;; Uses SIMD intrinsic to find \r\n\r\n (header end) quickly(let (header_end (buf.find_crlf_crlf buf))Connection Pooling
Section titled “Connection Pooling”Keep-alive connections reuse parser instances to avoid allocation overhead on high-throughput servers.
Binary Size Budget
Section titled “Binary Size Budget”The framework binary should stay under a configurable size limit (enforced by perf:regression-check). Zero-copy parsing and avoiding unnecessary abstractions help keep binary size down.
9. File Organization
Section titled “9. File Organization”When M14 code is written, the source tree will be organized as:
axon-web/├── src/│ ├── server.axs # Connection acceptor, HTTP/1.1 + HTTP/2 demultiplexing│ ├── router.axs # Radix tree, route matching, method dispatch│ ├── middleware.axs # Pipeline composition, built-in middleware│ ├── context.axs # WebContext struct, request/response accessors│ ├── json.axs # Encode/decode│ ├── websocket.axs # Protocol upgrade, frame handling│ ├── static.axs # File serving, MIME types, caching│ ├── template.axs # Compiled template engine│ ├── http2.axs # HPACK, stream multiplexing│ └── tls.axs # OpenSSL FFI bindings├── public/ # Landing page (deployed to GitLab Pages)├── tests/│ ├── test_router.py # Placeholder Python tests│ ├── test_middleware.py│ └── test_json.py├── .gitlab-ci.yml└── docs/ ├── architecture.md # This file ├── CONTRIBUTING.md └── ci.mdThis structure is planned, not implemented. No src/ directory exists yet.
10. CI Infrastructure
Section titled “10. CI Infrastructure”Already In Place
Section titled “Already In Place”| Job | Tool | What it validates |
|---|---|---|
lint:html | tidy | public/*.html (landing page, not framework) |
lint:css | stylelint@15.11.0 | public/**/*.css (landing page) |
lint:js | eslint@8.57.0 | public/**/*.js (landing page) |
pages | GitLab Pages | Deploys public/ to GitLab Pages |
All lint jobs have allow_failure: true — they validate the landing page, not the framework.
Framework CI (Dormant Until M14)
Section titled “Framework CI (Dormant Until M14)”When .axs source files exist in src/, these template jobs from labs/axon/axon-ci will activate:
| Job | Template | Purpose |
|---|---|---|
test:axon-check | axon-library.yml | Axon type checking |
test:axon-format | axon-library.yml | Code formatting check |
test:axon-e2e | axon-library.yml | Compile + run + golden file compare |
test:axon-test | axon-library.yml | Unit tests |
perf:regression-check | perf-thresholds.yml | Binary size and compile time gates |
perf:regression-check currently has allow_failure: true because there are no real benchmarks yet. It will be enforced when axon-web#9 (performance metrics and benchmarks) is implemented.