💡 1. Overview & Philosophy
Vela's standard web library, std/web, provides minimal
yet complete building blocks for backend services, full-duplex
WebSocket streaming, REST APIs, and HTTP client communications.
Inspired by the ergonomics and clarity of Go's
net/http and the low-overhead efficiency of Rust's
networking stacks, Vela combines straightforward abstractions with its
own unique language capabilities:
-
Scoped Request Regions (
@r): Memory allocated while processing an HTTP request lives in a dedicated per-request region. Once the response completes, the entire region is bulk-reclaimed instantly with zero garbage collection overhead or individual frees. -
Typed Failure Channels (
Response ~ WebError): Route handlers declare errors explicitly using Vela's failure channel operator~, allowing clean error propagation with the{ failure e ... }pattern without hidden exceptions. -
Kind-Based Pattern Matching: HTTP methods and error
states are represented by first-class kind definitions
(
Method,WebError), allowing exhaustive compile-time checking. -
Composable Decorator Middleware: Middleware layers
wrap route handlers cleanly using Vela's wrapper syntax
(
$(logging),$(authenticate)).
⚙️ 2. Concurrency Architecture & Event Loop
Network servers face a fundamental architectural choice when orchestrating concurrent I/O: multiplexing connections onto an asynchronous event loop or distributing connections across an operating system thread pool.
Single Event Loop (Vela's Model)
Mechanism: Non-blocking socket descriptors
multiplexed using OS primitives (epoll on Linux,
poll fallback).
Advantages:
- Zero Data Races: Handlers execute in cooperative turns; no mutex contention for shared server caches or routers.
- Minimal Footprint: Thousands of idle connections occupy only socket file descriptors (~few hundred bytes), avoiding thread stack allocations (typically 1–8 MiB per OS thread).
- Region Safety: Matches Vela's deterministic region-based memory lifecycle.
Persistent Connections (Keep-Alive)
Mechanism: Sockets remain open across sequential requests until idle timeout or connection limit is reached.
Advantages:
- Handshake Elimination: Reusing established TCP connections avoids repeated 3-way handshakes (SYN → SYN-ACK → ACK).
- TCP Congestion Window: Sockets maintain warmed-up congestion windows (cwnd), yielding high throughput for bursty API traffic.
-
Dual-Mode:
server.keep_alive(true)(default) orserver.keep_alive(false)for one-shot checkups.
🔌 3. Real-Time WebSockets (RFC 6455)
For bidirectional, full-duplex communication (live metric dashboards,
chat systems, collaborative editors, terminal sessions),
std/web provides complete native RFC 6455 WebSocket
support across both server and client roles.
Server-Side WebSocket Routing
Routes can be declared with app.ws(path, handler). When a
client sends an HTTP request with Upgrade: websocket, the
router automatically handles the cryptographic handshake and invokes
your handler with an open WebSocket:
include std/web { router, WebSocket, WebSocketMessage, Request, serve }
task main()
flex app = router()
; Register WebSocket endpoint at /ws/chat
app.ws("/ws/chat", task (ws: WebSocket, req: Request@r, r: region default)
show("client connected to websocket from ", req.header("user-agent") { "unknown" })
loop ws.is_open()
region msg_r default
fixed msg = ws.recv(msg_r) { failure _ break }
match msg
case WebSocketMessage.Text text_msg
show("received text: ", text_msg)
; Echo the message back to the client
ws.send_text("Echo: " + text_msg at msg_r, msg_r) { failure _ break }
case WebSocketMessage.Binary bin_data
show("received binary payload, size: ", bin_data.len)
ws.send_binary(bin_data, msg_r) { failure _ break }
case WebSocketMessage.Ping payload
; Automatic pong response
show("ping received")
case WebSocketMessage.Close code reason
show("client closed with code ", code, ": ", reason)
ws.close(code, "acknowledging close", msg_r) { failure _ }
break
case _
fi
fi
)
show("WebSocket server listening on http://127.0.0.1:8080/ws/chat")
serve("127.0.0.1:8080", app.route) { failure _ }
fi
Client-Side WebSocket Connections
Vela programs can also initiate outgoing WebSocket connections using
ws_connect(url, region). The client handles the
handshake, key generation, and RFC 6455 client masking automatically:
include std/web { ws_connect, WebSocketMessage }
task main()
region r default
fixed ws = ws_connect("ws://127.0.0.1:8080/ws/chat", r) { failure err
show("connection failed: ", err)
<-
}
; Send greeting message
ws.send_text("Hello from Vela client!", r) { failure _ }
; Await response
region msg_r default
fixed reply = ws.recv(msg_r) { failure _ WebSocketMessage.None }
match reply
case WebSocketMessage.Text payload
show("server replied: ", payload)
case _
show("no text reply received")
fi
; Gracefully close the connection
ws.close(1000, "work done", r) { failure _ }
fi
WebSocket API Reference
| Method | Parameters | Return Type | Description |
|---|---|---|---|
is_open() |
— | bool |
Returns true if the socket is connected and not
closed.
|
send_text(msg, r) |
msg: text, r: region default |
int ~ SocketError |
Encodes and transmits a UTF-8 text frame (opcode
0x1).
|
send_binary(data, r) |
data: text, r: region default |
int ~ SocketError |
Encodes and transmits a raw binary frame (opcode
0x2).
|
recv(into) |
into: region default |
WebSocketMessage@into ~ SocketError |
Receives and unmasks the next message frame, allocating its
payload in into.
|
ping(payload, r) |
payload: text, r: region default
|
int ~ SocketError |
Sends an RFC 6455 heartbeat ping frame (opcode
0x9).
|
pong(payload, r) |
payload: text, r: region default
|
int ~ SocketError |
Sends an RFC 6455 heartbeat pong frame (opcode
0xA).
|
close(code, reason, r) |
code: int, reason: text,
r: region default
|
nothing ~ SocketError |
Transmits a close frame (opcode 0x8) and closes the
underlying socket.
|
WebSocket Frame Framing & Masking Internals
RFC 6455 Compliance Highlights
-
Client-to-Server Masking: RFC 6455 mandates that
all frames originating from a client MUST be masked with a 4-byte
random masking key.
ws_connectandsend_texthandle this transparently inruntime/vela_rt.h. - Extended Payloads: Supports standard payloads up to 125 bytes, 16-bit extended payloads (up to 64 KiB), and 64-bit extended payloads for large streams.
-
Cryptographic Handshake: The
Sec-WebSocket-Keyheader is hashed with the standard RFC UUID (258EAFA5-E914-47DA-95CA-C5AB0DC85B11) via SHA-1 and Base64-encoded in native C runtime helpers. -
Zero-Copy Per-Message Lifecycles: By executing
region msg_r defaultinside message receive loops, payload buffers are allocated in a temporary region and reclaimed immediately without touching the garbage collector.
🌐 4. HTTP Server & Routing
Vela provides both declarative decorator-based routing and
programmatic Router instances:
include std/web { router, Request, Response, WebError, text_response, json_response, serve }
task get_user(req: Request@r, r: region default) -> Response@r ~ WebError@r
fixed id = req.param("id") { "0" }
<- text_response("User profile for ID: {id}" at r, r)
fi
task create_user(req: Request@r, r: region default) -> Response@r ~ WebError@r
fixed body = req.body()
show("creating user: ", body)
<- text_response("Created", r)
fi
task main()
flex app = router()
app.get("/users/{id}", get_user)
app.post("/users", create_user)
app.files("/assets/*", "public")
serve("127.0.0.1:8080", app.route) { failure err
show("server stopped: ", err)
}
fi
Continuous Listening with Server
In production systems or long-running daemons, use
Server.bind(host, port) and
server.serve(handler) to accept requests continuously:
include std/web { Server, Request, Response, text_response }
task main()
region r default
fixed srv = Server.bind("0.0.0.0", 8080, r) { failure err
show("bind failed: ", err)
<-
}
show("API server listening on 0.0.0.0:8080...")
srv.serve(task (req: Request@req_r, req_r: region default)
<- text_response("Welcome to Vela backend API", req_r)
) { failure err
show("serve terminated: ", err)
}
fi
📡 5. HTTP Client
std/web includes a high-performance HTTP client for REST
queries, microservice RPC, and external API requests:
include std/web { Client, fetch, fetch_json }
task main()
region r default
; Simple GET request
fixed resp = fetch("http://httpbin.org/get", r) { failure err
show("fetch failed: ", err)
<-
}
show("status: ", resp.status)
show("body: ", resp.body())
; POST JSON payload
fixed post_resp = fetch_json("http://httpbin.org/post", "{\"name\": \"Vela\", \"active\": true}", r) { failure _
<-
}
show("post status: ", post_resp.status)
fi
🔒 6. Security & Confinement
Serving files via app.files(prefix, directory) applies
strict path sanitization:
-
Directory traversal sequences (
..) are trapped and rejected with400 Bad Request. - Absolute paths escaping the root folder are forbidden.
-
Hidden files starting with
.are blocked from public dispatch.