view seobeo/docs/web_socket_server.md @ 279:b3b547563ec7

Add Google connector service and agent wiki Implement the C/Seobeo Google Drive and Gmail connector with encrypted OAuth storage, Zenbu authentication, browser testing, AI tool discovery, chunked HTTP decoding, and Bazel coverage. Consolidate repository guidance into progressive wiki documentation and enforce arena-first allocation for new first-party C code. Co-authored-by: Copilot <[email protected]> Copilot-Session: 84c338fd-0939-4bb3-b7f3-1062eb213e5d
author MrJuneJune <me@mrjunejune.com>
date Mon, 17 Aug 2026 22:22:36 -0700
parents 7b1719fa918c
children
line wrap: on
line source

# Seobeo WebSocket Server - Usage Guide

A clean, easy-to-use WebSocket server library that integrates seamlessly with the existing Seobeo HTTP server.

## Features

1. **Automatic Upgrade Handling**: Automatically detects and upgrades HTTP connections to WebSocket
2. **Route-Based Handlers**: Register WebSocket handlers for specific paths (like HTTP routing)
3. **Text and Binary Messages**: Send/receive both text and binary data
4. **Broadcast Support**: Send messages to all connected clients
5. **Connection Management**: Automatic connection tracking and cleanup
6. **Integrated with HTTP Server**: Works alongside existing HTTP routes and static file serving
7. **RFC 6455 Compliant**: Full WebSocket protocol support

## API Overview

### Initialization and Registration

```c
// Initialize WebSocket server system
void Seobeo_WebSocket_Server_Init();

// Register a WebSocket handler for a specific path
void Seobeo_WebSocket_Server_Register(const char *path,
    Seobeo_WebSocket_Server_Handler handler, void *p_user_data);
```

### Handler Function Type

```c
typedef void (*Seobeo_WebSocket_Server_Handler)(
  Seobeo_WebSocket_Server_Connection *p_conn,
  Seobeo_WebSocket_Message *p_msg,
  void *p_user_data
);
```

### Sending Messages

```c
// Send to specific client
int32 Seobeo_WebSocket_Server_Send_Text(Seobeo_WebSocket_Server_Connection *p_conn,
    const char *text);
int32 Seobeo_WebSocket_Server_Send_Binary(Seobeo_WebSocket_Server_Connection *p_conn,
    const uint8 *data, size_t length);

// Broadcast to all clients
void Seobeo_WebSocket_Server_Broadcast_Text(const char *text);
void Seobeo_WebSocket_Server_Broadcast_Binary(const uint8 *data, size_t length);
```

### Connection Management

```c
// Close specific connection
void Seobeo_WebSocket_Server_Connection_Close(Seobeo_WebSocket_Server_Connection *p_conn,
    uint16 code, const char *reason);
```

## Examples

### 1. Simple Echo Server

```c
#include "seobeo/seobeo.h"

void Echo_Handler(Seobeo_WebSocket_Server_Connection *p_conn,
    Seobeo_WebSocket_Message *p_msg, void *p_user_data)
{
  if (p_msg->opcode == SEOBEO_WS_OPCODE_TEXT)
  {
    printf("Received: %.*s\n", (int)p_msg->length, (char*)p_msg->data);
    Seobeo_WebSocket_Server_Send_Text(p_conn, (char*)p_msg->data);
  }
}

int main()
{
  // Initialize WebSocket routing
  Seobeo_WebSocket_Server_Init();

  // Register echo handler
  Seobeo_WebSocket_Server_Register("/echo", Echo_Handler, NULL);

  // Start HTTP server (automatically handles WebSocket upgrades)
  Seobeo_Web_Server_Start(NULL, "8080", SEOBEO_MODE_FORK, 0);

  return 0;
}
```

### 2. Chat Room Server

```c
void Chat_Handler(Seobeo_WebSocket_Server_Connection *p_conn,
    Seobeo_WebSocket_Message *p_msg, void *p_user_data)
{
  if (p_msg->opcode == SEOBEO_WS_OPCODE_TEXT)
  {
    char message[2048];
    snprintf(message, sizeof(message), "[%s]: %.*s",
        p_conn->client_id, (int)p_msg->length, (char*)p_msg->data);

    printf("Broadcasting: %s\n", message);
    Seobeo_WebSocket_Server_Broadcast_Text(message);
  }
}

int main()
{
  Seobeo_WebSocket_Server_Init();
  Seobeo_WebSocket_Server_Register("/chat", Chat_Handler, NULL);

  printf("Chat server started on ws://localhost:8080/chat\n");
  Seobeo_Web_Server_Start(NULL, "8080", SEOBEO_MODE_FORK, 0);

  return 0;
}
```

### 3. Binary Data Broadcasting

```c
void Binary_Handler(Seobeo_WebSocket_Server_Connection *p_conn,
    Seobeo_WebSocket_Message *p_msg, void *p_user_data)
{
  if (p_msg->opcode == SEOBEO_WS_OPCODE_BINARY)
  {
    printf("Received %zu bytes, broadcasting...\n", p_msg->length);
    Seobeo_WebSocket_Server_Broadcast_Binary(p_msg->data, p_msg->length);
  }
}

int main()
{
  Seobeo_WebSocket_Server_Init();
  Seobeo_WebSocket_Server_Register("/binary", Binary_Handler, NULL);

  Seobeo_Web_Server_Start(NULL, "8080", SEOBEO_MODE_FORK, 0);
  return 0;
}
```

### 4. Multiple Endpoints

```c
void Echo_Handler(Seobeo_WebSocket_Server_Connection *p_conn,
    Seobeo_WebSocket_Message *p_msg, void *p_user_data)
{
  if (p_msg->opcode == SEOBEO_WS_OPCODE_TEXT)
    Seobeo_WebSocket_Server_Send_Text(p_conn, (char*)p_msg->data);
}

void Chat_Handler(Seobeo_WebSocket_Server_Connection *p_conn,
    Seobeo_WebSocket_Message *p_msg, void *p_user_data)
{
  if (p_msg->opcode == SEOBEO_WS_OPCODE_TEXT)
  {
    char msg[2048];
    snprintf(msg, sizeof(msg), "[%s]: %.*s",
        p_conn->client_id, (int)p_msg->length, (char*)p_msg->data);
    Seobeo_WebSocket_Server_Broadcast_Text(msg);
  }
}

int main()
{
  Seobeo_WebSocket_Server_Init();

  // Register multiple WebSocket endpoints
  Seobeo_WebSocket_Server_Register("/echo", Echo_Handler, NULL);
  Seobeo_WebSocket_Server_Register("/chat", Chat_Handler, NULL);

  printf("Server started with:\n");
  printf("  ws://localhost:8080/echo - Echo server\n");
  printf("  ws://localhost:8080/chat - Chat room\n");

  Seobeo_Web_Server_Start(NULL, "8080", SEOBEO_MODE_FORK, 0);
  return 0;
}
```

### 5. Custom User Data

```c
typedef struct {
  int message_count;
  char name[64];
} ChatRoomData;

void Chat_Handler_With_Data(Seobeo_WebSocket_Server_Connection *p_conn,
    Seobeo_WebSocket_Message *p_msg, void *p_user_data)
{
  ChatRoomData *p_data = (ChatRoomData*)p_user_data;

  if (p_msg->opcode == SEOBEO_WS_OPCODE_TEXT)
  {
    p_data->message_count++;

    char message[2048];
    snprintf(message, sizeof(message), "[%s #%d]: %.*s",
        p_data->name, p_data->message_count,
        (int)p_msg->length, (char*)p_msg->data);

    Seobeo_WebSocket_Server_Broadcast_Text(message);
  }
}

int main()
{
  ChatRoomData room_data = {0};
  strcpy(room_data.name, "Main Room");

  Seobeo_WebSocket_Server_Init();
  Seobeo_WebSocket_Server_Register("/chat", Chat_Handler_With_Data, &room_data);

  Seobeo_Web_Server_Start(NULL, "8080", SEOBEO_MODE_FORK, 0);
  return 0;
}
```

### 6. Mixed HTTP and WebSocket Server

```c
// HTTP route handler
Seobeo_Request_Entry* Get_Status(Seobeo_Request_Entry *req, Dowa_Arena *arena)
{
  Seobeo_Request_Entry *resp = NULL;
  Dowa_HashMap_Push_Arena(resp, "status", "200", arena);
  Dowa_HashMap_Push_Arena(resp, "content-type", "application/json", arena);
  Dowa_HashMap_Push_Arena(resp, "body", "{\"status\": \"online\"}", arena);
  return resp;
}

// WebSocket handler
void WS_Handler(Seobeo_WebSocket_Server_Connection *p_conn,
    Seobeo_WebSocket_Message *p_msg, void *p_user_data)
{
  if (p_msg->opcode == SEOBEO_WS_OPCODE_TEXT)
    Seobeo_WebSocket_Server_Send_Text(p_conn, (char*)p_msg->data);
}

int main()
{
  // Initialize both HTTP and WebSocket routing
  Seobeo_Router_Init();
  Seobeo_WebSocket_Server_Init();

  // Register HTTP routes
  Seobeo_Router_Register("GET", "/api/status", Get_Status);

  // Register WebSocket routes
  Seobeo_WebSocket_Server_Register("/ws", WS_Handler, NULL);

  printf("Server started:\n");
  printf("  HTTP: http://localhost:8080/api/status\n");
  printf("  WebSocket: ws://localhost:8080/ws\n");

  Seobeo_Web_Server_Start("./public", "8080", SEOBEO_MODE_FORK, 0);
  return 0;
}
```

## Building

### Build the server example:
```bash
bazel build //seobeo:websocket_server_example
```

### Run the server:
```bash
bazel-bin/seobeo/websocket_server_example
```

### Test with a client:
You can test the server using the WebSocket client API or any WebSocket client tool:
```bash
# Using websocat (install: cargo install websocat)
websocat ws://localhost:8080/echo

# Using wscat (install: npm install -g wscat)
wscat -c ws://localhost:8080/chat
```

## Connection Structure

```c
struct Seobeo_WebSocket_Server_Connection_Struct {
  Seobeo_Handle *p_handle;      // Underlying socket handle
  char          *client_id;     // Unique client identifier
  boolean        is_active;     // Connection status

  // Fragment handling (internal)
  uint8  *fragment_buffer;
  size_t  fragment_length;
  size_t  fragment_capacity;
  Seobeo_WebSocket_Opcode fragment_opcode;

  Seobeo_WebSocket_Server_Connection *next;  // Linked list
};
```

## Protocol Details

The server implementation follows RFC 6455:

1. **Handshake**: Responds to HTTP Upgrade requests with proper Sec-WebSocket-Accept key
2. **Frame Format**: Server sends unmasked frames (RFC requirement)
3. **Frame Receiving**: Server receives and unmasks client frames
4. **Fragmentation**: Handles fragmented messages automatically
5. **Control Frames**: Proper handling of ping, pong, and close frames
6. **Broadcast**: Efficiently sends to all active connections

## Key Differences from Client

| Feature | Client | Server |
|---------|--------|--------|
| Frame Masking | Masks outgoing frames | Does NOT mask outgoing frames |
| Connection | Initiates connection | Accepts connections |
| Upgrade | Sends upgrade request | Responds to upgrade request |
| Multiple Connections | Single connection | Manages multiple connections |
| Broadcast | N/A | Can broadcast to all clients |

## Coding Standards

The implementation follows your specified coding standards:
- Naming: `Seobeo_WebSocket_Server_Init`, `Seobeo_WebSocket_Server_Send_Text`
- Two spaces for indentation
- New line before `{` unless it's a struct
- Single statement: no need for `{}`

## Integration with Existing Server

The WebSocket server integrates seamlessly:

1. **Automatic Detection**: The HTTP server automatically checks for WebSocket upgrade requests
2. **No Conflicts**: WebSocket and HTTP routes coexist peacefully
3. **Same Port**: WebSocket and HTTP share the same server port
4. **Unified Server**: Use `Seobeo_Web_Server_Start()` for everything

## Testing the Server

### JavaScript Client (Browser):
```javascript
const ws = new WebSocket('ws://localhost:8080/echo');

ws.onopen = () => {
  console.log('Connected!');
  ws.send('Hello, Server!');
};

ws.onmessage = (event) => {
  console.log('Received:', event.data);
};
```

### Python Client:
```python
import websocket

ws = websocket.create_connection("ws://localhost:8080/chat")
ws.send("Hello from Python!")
print(ws.recv())
ws.close()
```

## Performance Considerations

1. **Fork Mode**: Each connection runs in a separate process (SEOBEO_MODE_FORK)
2. **Edge Mode**: Multi-threaded connections (SEOBEO_MODE_EDGE)
3. **Broadcast**: Iterates through all connections (O(n) complexity)
4. **Fragmentation**: Large messages are automatically fragmented (1MB chunks)

## Complete Feature Comparison

| Feature | Implemented |
|---------|-------------|
| ✅ WebSocket upgrade handshake | Yes |
| ✅ Text messages | Yes |
| ✅ Binary messages | Yes |
| ✅ Message fragmentation | Yes |
| ✅ Ping/Pong | Yes |
| ✅ Close handshake | Yes |
| ✅ Multiple endpoints | Yes |
| ✅ Broadcast | Yes |
| ✅ Connection tracking | Yes |
| ✅ Integration with HTTP server | Yes |
| ✅ Custom user data per handler | Yes |

The Seobeo WebSocket Server provides a complete, production-ready WebSocket implementation that works seamlessly with the existing HTTP server!