The WebSocket API lets a Vix application keep a real-time connection open between a client and the server.
HTTP → request/response
WebSocket → bidirectional live connection#include <vix/websocket.hpp>
#include <vix.hpp> // for HTTP routes
#include <vix/websocket/AttachedRuntime.hpp> // for combined HTTP + WebSocketConfig → RuntimeExecutor → App + WebSocket Server → run_http_and_ws(...)#include <vix.hpp>
#include <vix/websocket.hpp>
int main()
{
vix::config::Config cfg{".env"};
auto executor = std::make_shared<vix::executor::RuntimeExecutor>(1u);
vix::App app{executor};
vix::websocket::Server ws{cfg, executor};
app.get("/health", [](vix::Request &, vix::Response &res){
res.json({
"ok", true,
"service", "websocket-api"
});
});
ws.on_open([](vix::websocket::Session &) {
std::cout << "client connected\n";
});
ws.on_close([](vix::websocket::Session &) {
std::cout << "client disconnected\n";
});
ws.on_error([](vix::websocket::Session &, const std::string &e) {
std::cout << "error: " << e << "\n";
});
vix::run_http_and_ws(app, ws, executor, cfg);
return 0;
}SERVER_PORT=8080
SERVER_TLS_ENABLED=false
WEBSOCKET_MAX_MESSAGE_SIZE=65536
WEBSOCKET_IDLE_TIMEOUT=60
WEBSOCKET_ENABLE_DEFLATE=true
WEBSOCKET_PING_INTERVAL=30
WEBSOCKET_AUTO_PING_PONG=truews.on_open([&ws](vix::websocket::Session &session) {
/* client connected */
});
ws.on_close([](vix::websocket::Session &session) {
/* client disconnected */
});
ws.on_error([](vix::websocket::Session &session, const std::string &error) {
/* error */
});Client sends:
{ "type": "chat.message", "payload": { "user": "Ada", "text": "Hello" } }Server handles:
ws.on_typed_message(
[&ws](vix::websocket::Session &session,
const std::string &type,
const vix::json::kvs &payload)
{
if (type == "app.ping")
{
ws.broadcast_json(
"app.pong", {"status", "ok", "transport", "websocket"}
);
return;
}
if (type == "chat.message")
{
const std::string text = payload.get_string_or("text", "");
if (text.empty())
{
ws.broadcast_json(
"chat.error", {"error", "text is required"}
);
return;
}
ws.broadcast_json(
"chat.message", payload
);
return;
}
ws.broadcast_json(
"app.unknown", {"type", type, "message", "Unknown event type"
});
});ws.broadcast_json(
"system.connected", {"message", "A client connected"}
);
ws.broadcast_json(
"chat.message", payload
);| Event | Direction | Purpose |
|---|---|---|
app.ping |
client → server | Health check |
app.pong |
server → client | Health response |
chat.join |
client → server | User joins |
chat.leave |
client → server | User leaves |
chat.message |
both | Chat message |
chat.error |
server → client | Chat error |
system.connected |
server → client | Client connected |
system.disconnected |
server → client | Client disconnected |
// Full control
vix::run_http_and_ws(app, ws, executor, cfg);
// Compact form
vix::serve_http_and_ws(".env", 8080, [](auto &app, auto &ws){
app.get("/", [](auto &, auto &res) { res.json({{"framework", "Vix.cpp"}}); });
ws.on_typed_message([&ws](auto &, const std::string &type, const vix::json::kvs &payload){
if (type == "chat.message")
ws.broadcast_json(
"chat.message", payload
);
else
ws.broadcast_json(
"app.unknown", {"type", type}
);
});
});| Variable | Purpose | Default |
|---|---|---|
WEBSOCKET_MAX_MESSAGE_SIZE |
Max message size in bytes | 65536 |
WEBSOCKET_IDLE_TIMEOUT |
Idle timeout in seconds | 60 |
WEBSOCKET_ENABLE_DEFLATE |
Per-message deflate | true |
WEBSOCKET_PING_INTERVAL |
Ping interval in seconds | 30 |
WEBSOCKET_AUTO_PING_PONG |
Automatic ping/pong | true |
vix::websocket::Config ws_cfg = vix::websocket::Config::from_core(core_cfg);
std::cout << ws_cfg.maxMessageSize << "\n";location /ws/ {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 3600;
proxy_send_timeout 3600;
}Without upgrade headers, WebSocket connections fail through Nginx.
| HTTP | WebSocket | P2P | |
|---|---|---|---|
| Pattern | Request/response | Bidirectional | Node-to-node |
| Best for | CRUD APIs | Live UI updates | Distributed sync |
| Discovery | Manual | Manual | Auto (UDP) |
static void register_ws_lifecycle(vix::websocket::Server &ws)
{
ws.on_open([&ws](vix::websocket::Session &){
ws.broadcast_json("system.connected", {"message", "A client connected"});
});
ws.on_close([&ws](vix::websocket::Session &){
ws.broadcast_json("system.disconnected", {"message", "A client disconnected"});
});
ws.on_error([](vix::websocket::Session &, const std::string &e){
std::cout << "websocket error: " << e << "\n";
});
}
static void register_ws_protocol(vix::websocket::Server &ws)
{
ws.on_typed_message([&ws](vix::websocket::Session &,
const std::string &type,
const vix::json::kvs &payload)
{
if (type == "app.ping") {
ws.broadcast_json(
"app.pong", {"status", "ok"}
);
return;
}
if (type == "chat.join") {
ws.broadcast_json(
"chat.join", payload
);
return;
}
if (type == "chat.leave") {
ws.broadcast_json(
"chat.leave", payload
);
return;
}
if (type == "chat.message")
{
if (payload.get_string_or("text", "").empty())
{
ws.broadcast_json(
"chat.error", {"error", "text is required"}
);
return;
}
ws.broadcast_json(
"chat.message", payload
);
return;
}
ws.broadcast_json(
"app.unknown", {"type", type, "message", "Unknown event type"}
);
});
}- Validate message type and payload fields
- Set message size limits
- Configure idle timeout and ping/pong
- Authenticate clients (token, cookie, or signed handshake)
- Never trust user-supplied identity from payload
- Avoid broadcasting private data
- Configure Nginx upgrade headers
- Use HTTPS/WSS publicly
Always validate before broadcasting.
Use HTTP for CRUD APIs. Use WebSocket for live events only.
Even real-time apps need HTTP health checks.
Always have a fallback returning app.unknown.
vix::config::Config cfg{".env"};
auto executor = std::make_shared<vix::executor::RuntimeExecutor>(1u);
vix::App app{executor};
vix::websocket::Server ws{cfg, executor};
vix::run_http_and_ws(app, ws, executor, cfg);HTTP handles requests. WebSocket handles live events.
Next: Async API