Vela Web Architecture

High-performance HTTP/1.1 microservices, REST APIs, static file delivery, and native RFC 6455 WebSockets with scoped request regions and zero garbage collection.

HTTP/1.1 Keep-Alive RFC 6455 WebSockets Zero-GC Request Regions

💡 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:

⚙️ 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) or server.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_connect and send_text handle this transparently in runtime/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-Key header 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 default inside 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: