Skip to content

WebSocket

Lee Yunjin edited this page Oct 7, 2026 · 1 revision

WebSocket

Headers: <cwist/net/websocket/websocket.h> (blocking API), <cwist/net/websocket/websocket_async.h> (reactor API), <cwist/app.h>

CWIST has two WebSocket APIs:

Blocking (cwist_app_ws) Reactor (cwist_app_ws_async)
Handler shape one function that owns the connection and loops on receive a callback per complete message
Server modes reactor and classic pool reactor only; in classic mode such a route answers 501
Cost per connection a thread while the handler runs a reactor slot
Use for simple request/response protocols many concurrent connections (chat, live updates)

Blocking API

static void echo(cwist_websocket *ws) {
    cwist_ws_frame *f;
    while ((f = cwist_websocket_receive(ws)) != NULL) {
        if (f->opcode == CWIST_WS_FRAME_TEXT || f->opcode == CWIST_WS_FRAME_BINARY)
            cwist_websocket_send(ws, f->opcode, f->payload, f->payload_len);
        bool closing = f->opcode == CWIST_WS_FRAME_CLOSE;
        cwist_websocket_frame_destroy(f);
        if (closing) break;
    }
}

cwist_app_ws(app, "/echo", echo);

The framework performs the handshake and passes the upgraded connection to the handler; the connection closes when the handler returns.

Function Description
cwist_ws_frame *cwist_websocket_receive(cwist_websocket *ws) Block until a complete message arrives; fragmented messages (RFC 6455 section 5.4) are reassembled. NULL on close or error. Free the frame with cwist_websocket_frame_destroy().
int cwist_websocket_send(cwist_websocket *ws, cwist_ws_opcode_t opcode, const uint8_t *data, size_t len) Send one frame. 0 or -1.
int cwist_websocket_send_sequenced(cwist_websocket *ws, const uint8_t *data, size_t len, uint16_t chunk_payload_size) Split a large payload into binary frames with a sequence header (Sequenced Messages).
uint8_t *cwist_websocket_receive_sequenced(cwist_websocket *ws, size_t *out_len) Receive and reassemble such a message (duplicates dropped, reordering handled). Free with cwist_free().
void cwist_websocket_close(cwist_websocket *ws) Send a close frame.
cwist_websocket *cwist_websocket_upgrade(cwist_http_request *req, int client_fd) / void cwist_websocket_destroy(cwist_websocket *ws) Upgrade a request yourself (outside cwist_app_ws) and free the context.
bool cwist_websocket_upgrade_response(cwist_http_request *req, cwist_http_response *res) Validate an upgrade request and fill a 101 response without sending it.

Opcodes: CWIST_WS_FRAME_CONTINUATION, _TEXT, _BINARY, _CLOSE, _PING, _PONG. A frame has fin, opcode, payload and payload_len.

Reactor API

static void on_message(cwist_websocket_async *ws, cwist_ws_frame *frame, void *user_data) {
    (void)user_data;
    if (frame->opcode == CWIST_WS_FRAME_TEXT)
        cwist_websocket_async_send(ws, CWIST_WS_FRAME_TEXT, frame->payload, frame->payload_len);
    cwist_websocket_frame_destroy(frame);   /* the callback must free the frame */
}

cwist_app_ws_async(app, "/live", on_message, NULL);
Function Description
void cwist_app_ws_async(cwist_app *app, const char *path, cwist_ws_on_message_t on_message, void *user_data) Register a reactor-driven route; on_message(ws, frame, user_data) runs on the reactor thread for every complete message.
int cwist_websocket_async_send(cwist_websocket_async *ws, cwist_ws_opcode_t opcode, const uint8_t *data, size_t len) Send one frame without blocking; an unsent remainder is parked on a one-shot write slot. 0 or -1.
void cwist_websocket_async_close(cwist_websocket_async *ws) Queue a close frame and schedule cleanup; safe inside the callback.

Callbacks run on the reactor thread, so they must not block. Idle reactor WebSocket connections close after CWIST_WS_ASYNC_IDLE_TIMEOUT_SEC (default 300; 0 disables).

GraphQL subscriptions

The graphql-transport-ws protocol runs on the reactor WebSocket transport; see GraphQL.

Clone this wiki locally