ascii-chat
Binary v0.15.83-53-gb711d8720
Library lib/v0.3.2-701-gb711d8720
Video chat in your terminal
Loading...
Searching...
No Matches
server.h File Reference

WebSocket server for accepting browser client connections. More...

Go to the source code of this file.

Data Structures

struct  websocket_client_context_t
 Listener settings and callbacks used to create a WebSocket server. More...
struct  websocket_server_config_t
 Listener settings and callbacks used to create a WebSocket server. More...
struct  websocket_server
 Runtime state for the WebSocket listener and its client-handler pool. More...

Typedefs

typedef struct websocket_server websocket_server_t
typedef struct thread_pool thread_pool_t
 One queued function call waiting for a thread-pool worker.
typedef void *(* websocket_client_handler_fn) (void *arg)
 Connection-specific values handed to a WebSocket client worker.

Functions

asciichat_error_t websocket_server_init (websocket_server_t *server, const websocket_server_config_t *config)
 Initialize WebSocket server.
asciichat_error_t websocket_server_run (websocket_server_t *server)
 Run WebSocket server event loop.
void websocket_server_destroy (websocket_server_t *server)
 Destroy WebSocket server and free resources.
void websocket_server_cancel_service (websocket_server_t *server)
 Wake the WebSocket server event loop immediately.

Detailed Description

WebSocket server for accepting browser client connections.

Provides a WebSocket server implementation using libwebsockets to accept connections from browser-based WASM clients alongside the TCP server.

This module mirrors the tcp_server API to provide consistent server-side connection handling regardless of transport type.

Usage Pattern

  1. Configure server with websocket_server_config_t
  2. Call websocket_server_init() to create libwebsockets context
  3. Call websocket_server_run() to start event loop (blocks)
  4. Signal shutdown by setting running flag to false
  5. Call websocket_server_destroy() to clean up

Example

// Define client handler (same as TCP)
void *my_client_handler(void *arg) {
// Process client connection via transport
SAFE_FREE(ctx);
return NULL;
}
// Configure and run server
.port = 27224,
.client_handler = my_client_handler,
.user_data = NULL
};
if (websocket_server_init(&server, &config) == ASCIICHAT_OK) {
}
#define SAFE_FREE(ptr)
Definition common.h:377
@ ASCIICHAT_OK
Definition error_codes.h:46
struct websocket_server websocket_server_t
asciichat_error_t websocket_server_init(websocket_server_t *server, const websocket_server_config_t *config)
Initialize WebSocket server.
asciichat_error_t websocket_server_run(websocket_server_t *server)
Run WebSocket server event loop.
void websocket_server_destroy(websocket_server_t *server)
Destroy WebSocket server and free resources.
Listener settings and callbacks used to create a WebSocket server.
acip_transport_t * transport
ACIP transport for this client.
Listener settings and callbacks used to create a WebSocket server.
void acip_transport_destroy(acip_transport_t *transport)
Destroy transport and free all resources.
Author
Zachary Fogg me@zf.nosp@m.o.gg
Date
February 2026

Definition in file include/ascii-chat/network/websocket/server.h.

Typedef Documentation

◆ thread_pool_t

typedef struct thread_pool thread_pool_t

One queued function call waiting for a thread-pool worker.

Thread handle and shutdown metadata for one pool-owned thread

This is the handoff point for thread pool. Callers fill threads, threads_mutex, thread_count, is_work_queue_mode before passing it on; consumers read the values during the operation and return ownership only where the member contract explicitly says they do.

Definition at line 63 of file include/ascii-chat/network/websocket/server.h.

◆ websocket_client_handler_fn

typedef void *(* websocket_client_handler_fn) (void *arg)

Connection-specific values handed to a WebSocket client worker.

Runtime state for the WebSocket listener and its client-handler pool

Client handler function type

Called when a new WebSocket client connects. The handler receives a fully initialized transport ready for ACIP packets.

Parameters
argwebsocket_client_context_t* containing connection info
Returns
Thread exit value (ignored)

Definition at line 76 of file include/ascii-chat/network/websocket/server.h.

◆ websocket_server_t

Function Documentation

◆ websocket_server_cancel_service()

void websocket_server_cancel_service ( websocket_server_t * server)

Wake the WebSocket server event loop immediately.

Thread-safe. Wakes lws_service() from its timeout wait, allowing the event loop to check the running flag without waiting for the full service timeout (50ms).

Parameters
serverServer instance (must have a valid context)

Definition at line 868 of file lib/network/websocket/server.c.

868 {
869 if (server && server->context) {
870 lws_cancel_service(server->context);
871 }
872}
struct lws_context * context
libwebsockets context

References websocket_server::context.

Referenced by network_server_stop().

◆ websocket_server_destroy()

void websocket_server_destroy ( websocket_server_t * server)

Destroy WebSocket server and free resources.

Parameters
serverServer instance to destroy

Definition at line 874 of file lib/network/websocket/server.c.

874 {
875 if (!server) {
876 return;
877 }
878
879 atomic_store_bool(&server->running, false);
880
881 // Cancel any pending libwebsockets service calls to interrupt blocking lws_service()
882 // This prevents the event loop from getting stuck in lws_service()
883 if (server->context) {
884 log_debug("Cancelling libwebsockets service");
885 lws_cancel_service(server->context);
886 }
887
888 // Unregister atomic fields
889 NAMED_UNREGISTER(&server->running);
890
891 if (server->context) {
892 NAMED_UNREGISTER(server->context);
893 lws_context_destroy(server->context);
894 server->context = NULL;
895 }
896 if (server->handler_pool) {
898 server->handler_pool = NULL;
899 }
900
901 log_debug("WebSocket server destroyed");
902}
#define NAMED_UNREGISTER(ptr)
Unregister a pointer.
#define log_debug(...)
Log a DEBUG message.
Definition log/log.h:595
struct thread_pool * handler_pool
Thread pool for queueing handler work.
void thread_pool_destroy(thread_pool_t *pool)
Destroy a thread pool.

References websocket_server::context, websocket_server::handler_pool, log_debug, NAMED_UNREGISTER, websocket_server::running, and thread_pool_destroy().

Referenced by network_server_stop().

◆ websocket_server_init()

asciichat_error_t websocket_server_init ( websocket_server_t * server,
const websocket_server_config_t * config )

Initialize WebSocket server.

Creates libwebsockets context and prepares for accepting connections.

Parameters
serverServer instance to initialize
configServer configuration
Returns
ASCIICHAT_OK on success, error code on failure

Definition at line 703 of file lib/network/websocket/server.c.

703 {
704 if (!server || !config || !config->client_handler) {
705 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters");
706 }
707
708 memset(server, 0, sizeof(*server));
709 server->handler = config->client_handler;
710 server->user_data = config->user_data;
711 server->port = config->port;
712 atomic_store_bool(&server->running, true);
713
714 // Store server pointer in protocol user data so callbacks can access it
715 websocket_protocols[0].user = server; // http protocol
716 websocket_protocols[1].user = server; // acip protocol
717
718 // Enable libwebsockets logging through centralized logging system
720
721 // HTTP mount for /health endpoint (required for LWS_CALLBACK_HTTP to fire)
722 static const struct lws_http_mount health_mount = {
723 .mount_next = NULL,
724 .mountpoint = "/",
725 .mountpoint_len = 1,
726 .origin = "http",
727 .def = NULL,
728 .protocol = "http",
729 .origin_protocol = LWSMPRO_CALLBACK,
730 };
731
732 // Configure libwebsockets context
733 struct lws_context_creation_info info = {0};
734 info.port = config->port;
735 info.iface = config->bind_address;
736 info.protocols = websocket_protocols;
737 info.mounts = &health_mount;
738 info.gid = (gid_t)-1; // Cast to avoid undefined behavior with unsigned type
739 info.uid = (uid_t)-1; // Cast to avoid undefined behavior with unsigned type
740 info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT; // Initialize SSL/TLS support (required for server binding)
741 info.extensions = NULL; // Disable permessage-deflate - causing connection issues
742 info.retry_and_idle_policy = &keep_alive_policy; // Configure keep-alive to prevent idle disconnects during handshake
743
744 // Configure TLS/WSS support if certificates are provided (check for non-empty strings)
745 if (config->tls_cert_path && config->tls_cert_path[0] != '\0' && config->tls_key_path &&
746 config->tls_key_path[0] != '\0') {
747#ifdef LWS_WITH_TLS
748 // Validate certificate and key files are readable
749 if (platform_access(config->tls_cert_path, R_OK) != 0) {
750 return SET_ERRNO(ERROR_NETWORK_BIND, "TLS certificate file not readable: %s (errno=%d)", config->tls_cert_path,
751 errno);
752 }
753 if (platform_access(config->tls_key_path, R_OK) != 0) {
754 return SET_ERRNO(ERROR_NETWORK_BIND, "TLS key file not readable: %s (errno=%d)", config->tls_key_path, errno);
755 }
756
757 info.ssl_cert_filepath = config->tls_cert_path;
758 info.ssl_private_key_filepath = config->tls_key_path;
759 server->tls_enabled = true;
760 log_info("WebSocket server configured for WSS (TLS): cert=%s, key=%s", config->tls_cert_path, config->tls_key_path);
761#else
762 log_warn("WebSocket server: TLS support not compiled in libwebsockets; WSS unavailable");
763#endif
764 } else if ((config->tls_cert_path && config->tls_cert_path[0] != '\0') ||
765 (config->tls_key_path && config->tls_key_path[0] != '\0')) {
766 NOTICE(DANGER, "WEBSOCKET TLS DISABLED",
767 "WebSocket server: Both TLS certificate and key must be provided for WSS; using plain WS");
768 }
769
770 // Increase per-thread service buffer to prevent fragmentation of large messages
771 // Default is 4KB, causing large frames to fragment into many chunks
772 // Video frames are 921KB, so increase to 2MB to handle them without fragmentation
773 info.pt_serv_buf_size = 2 * 1024 * 1024; // 2MB per-thread service buffer
774
775 // Disable ALL default timeouts and keep-alive mechanisms
776 // Use only the explicit retry_and_idle_policy we set above (30/35 seconds)
777 info.ka_time = 0; // Disable TCP keep-alive probes (use WebSocket pings instead)
778 info.ka_probes = 0; // Disable TCP probes
779 info.ka_interval = 0; // Disable TCP probe intervals
780 info.keepalive_timeout = 0; // Disable HTTP keep-alive timeout entirely
781
782 // Explicitly set a very large idle timeout to prevent early disconnection
783 // Without this, libwebsockets defaults to 5 seconds for HTTP connections
784 // Set to effectively infinite (100 hours) since we use retry_and_idle_policy instead
785 info.timeout_secs = 360000; // 100 hours - effectively disabled
786
787 // Create libwebsockets context
788 // Log diagnostic info before attempting creation
789 log_debug("lws_create_context: port=%u, protocols=%p, options=0x%x", info.port, (void *)info.protocols, info.options);
790
791 server->context = lws_create_context(&info);
792 if (!server->context) {
793 // Capture any OpenSSL errors that may have occurred during context creation
794 unsigned long ssl_err = 0;
795 char ssl_err_str[NETWORK_WEBSOCKET_SERVER_SSL_ERR_BUFFER_SIZE] = "no OpenSSL errors in queue";
796
797 // Extract all errors from OpenSSL error queue
798 while ((ssl_err = ERR_get_error()) != 0) {
799 ERR_error_string_n(ssl_err, ssl_err_str, sizeof(ssl_err_str));
800 log_error("OpenSSL error: %lu: %s", ssl_err, ssl_err_str);
801 }
802
803 return SET_ERRNO(ERROR_NETWORK_BIND, "Failed to create libwebsockets context (last OpenSSL error: %s)",
804 ssl_err_str);
805 }
806
807 // Each connection owns a handler until disconnect; provide a worker for every participant.
809
810 if (!server->handler_pool) {
811 lws_context_destroy(server->context);
812 server->context = NULL;
813 return SET_ERRNO(ERROR_THREAD, "Failed to create WebSocket handler thread pool");
814 }
815
816 /* Register WebSocket server with named registry */
818 snprintf(ws_port_name, sizeof(ws_port_name), "ws:%d", server->port);
819 NAMED_REGISTER(server, ws_port_name, "websocket_server", "0x%tx", NULL);
820
821 /* Register server's sync primitives with hierarchical naming */
822 NAMED_REGISTER_ATOMIC(&server->running, "is_running", (uintptr_t)(const void *)(server));
823
824 /* Register WebSocket context as child of server */
825 NAMED_REGISTER_WEBSOCKET(server->context, "context", (uintptr_t)(const void *)(server));
826
827 log_info("WebSocket server initialized on port %d with static file serving", config->port);
828 return ASCIICHAT_OK;
829}
#define SET_ERRNO(code,...)
#define NAMED_REGISTER_ATOMIC(a, name, parent_ptr)
Register an atomic_t with automatic format specifier.
#define NAMED_REGISTER(ptr, name, type, fmt, parent_ptr)
Register any pointer with base name, type, format spec, and location (auto-suffix).
#define NAMED_REGISTER_WEBSOCKET(websocket, name, parent_ptr)
Register a WebSocket connection with automatic format specifier.
@ ERROR_NETWORK_BIND
Definition error_codes.h:73
@ ERROR_INVALID_PARAM
@ ERROR_THREAD
#define MAX_SESSION_PARTICIPANTS
#define log_warn(...)
Log a WARN message.
Definition log/log.h:621
#define log_error(...)
Log an ERROR message.
Definition log/log.h:634
#define log_info(...)
Log an INFO message.
Definition log/log.h:608
int errno
#define NETWORK_WEBSOCKET_SERVER_WS_PORT_NAME_BUFFER_SIZE
#define NETWORK_WEBSOCKET_SERVER_SSL_ERR_BUFFER_SIZE
#define NOTICE(severity, title,...)
Definition notice.h:68
websocket_client_handler_fn client_handler
Handler for new connections.
const char * bind_address
Numeric local address to listen on (never a wildcard by default).
const char * tls_cert_path
TLS certificate file path (optional, for wss://).
const char * tls_key_path
TLS private key file path (optional, for wss://).
websocket_client_handler_fn handler
Client handler function.
bool tls_enabled
True when this listener is configured with TLS credentials.
thread_pool_t * thread_pool_create_with_workers(const char *pool_name, size_t num_workers)
Create a new thread pool in work queue mode.
void lws_log_init_server(void)
Initialize libwebsockets logging for server mode.
Definition websocket.c:87

References ASCIICHAT_OK, websocket_server_config_t::bind_address, websocket_server_config_t::client_handler, websocket_server::context, errno, ERROR_INVALID_PARAM, ERROR_NETWORK_BIND, ERROR_THREAD, websocket_server::handler, websocket_server::handler_pool, log_debug, log_error, log_info, log_warn, lws_log_init_server(), MAX_SESSION_PARTICIPANTS, NAMED_REGISTER, NAMED_REGISTER_ATOMIC, NAMED_REGISTER_WEBSOCKET, NETWORK_WEBSOCKET_SERVER_SSL_ERR_BUFFER_SIZE, NETWORK_WEBSOCKET_SERVER_WS_PORT_NAME_BUFFER_SIZE, NOTICE, websocket_server::port, websocket_server_config_t::port, R_OK, websocket_server::running, SET_ERRNO, thread_pool_create_with_workers(), websocket_server_config_t::tls_cert_path, websocket_server::tls_enabled, websocket_server_config_t::tls_key_path, websocket_server::user_data, and websocket_server_config_t::user_data.

Referenced by network_server_create().

◆ websocket_server_run()

asciichat_error_t websocket_server_run ( websocket_server_t * server)

Run WebSocket server event loop.

Blocks until server is signaled to stop via running flag.

Parameters
serverServer instance
Returns
ASCIICHAT_OK on success, error code on failure

Definition at line 831 of file lib/network/websocket/server.c.

831 {
832 if (!server || !server->context) {
833 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid server");
834 }
835
836 log_info("WebSocket server starting event loop on port %d", server->port);
837
838 // Run libwebsockets event loop
839 uint64_t last_service_ns = 0;
840 int service_call_count = 0;
841 while (atomic_load_bool(&server->running)) {
842 // Service libwebsockets with 16ms timeout to match 60 FPS frame rate.
843 // This provides frequent event processing (~60 callback invocations per second) so that
844 // WebSocket frames queued by the render loop are sent promptly, matching TCP performance.
845 // All client connections share this single server context, so a single lws_service() call
846 // services fragments and events for all clients simultaneously.
847 uint64_t service_start_ns = time_get_ns();
848 if (last_service_ns && service_start_ns - last_service_ns > 30 * US_PER_MS_INT) {
849 // > 30ms gap between service calls
850 double gap_ms = (double)(service_start_ns - last_service_ns) / 1e6;
851 log_debug_every(US_PER_SEC_INT, "[LWS_SERVICE_GAP] %.1fms gap between lws_service calls", gap_ms);
852 }
853 service_call_count++;
854 log_debug_every(500 * US_PER_MS_INT, "[LWS_SERVICE] Call #%d, context=%s", service_call_count,
855 NAMED_DESCRIBE(server->context, "websocket_server"));
856 int result = lws_service(server->context, 1);
857 if (result < 0) {
858 log_error("libwebsockets service error: %d", result);
859 break;
860 }
861 }
862
863 log_info(
864 "WebSocket server event loop exited (context will be destroyed by main thread after handler threads complete)");
865 return ASCIICHAT_OK;
866}
unsigned long long uint64_t
Definition common.h:63
#define NAMED_DESCRIBE(ptr, hint)
Describe a pointer in log format.
#define US_PER_MS_INT
Definition time.h:172
#define US_PER_SEC_INT
Definition time.h:173
#define log_debug_every(interval_us, fmt,...)
Rate-limited DEBUG logging.
Definition log/log.h:749

References ASCIICHAT_OK, websocket_server::context, ERROR_INVALID_PARAM, log_debug_every, log_error, log_info, NAMED_DESCRIBE, websocket_server::port, websocket_server::running, SET_ERRNO, US_PER_MS_INT, and US_PER_SEC_INT.