Enhance WebSocket support: add opcode handling, binary message support, and improve connection validation

This commit is contained in:
udo
2026-04-18 18:41:50 +00:00
parent 86dc93864e
commit 46d98a092f
25 changed files with 623 additions and 69 deletions
+20
View File
@@ -0,0 +1,20 @@
:sig
WS()
:desc
Defines the WebSocket message handler for the current `.ws.uce` page.
The same page may expose both `RENDER()` and `WS()`. `RENDER()` serves the initial HTTP response, while `WS()` is called whenever a complete WebSocket message arrives for that page.
UCE reassembles fragmented messages before calling `WS()`. Text and binary frames are both delivered. Use `ws_opcode()` and `ws_is_binary()` to distinguish them.
The `call` parameter passed into `WS()` contains:
- `message`
- `connection_id`
- `scope`
- `opcode`
- `document_uri`
:see
>websocket
+3 -2
View File
@@ -1,8 +1,9 @@
:sig
String session_start(String session_name)
String session_start(String session_name = "uce-session")
:params
return value : the session ID, defaults to "uce-session"
session_name : optional name of the session cookie, defaults to "uce-session"
return value : the current session ID
:desc
Starts session or connects to existing session. This function sets a cookie with the name contained in 'session_name' if it does not exist and fills that cookie with a new unique session ID. It then loads the session data for that session ID. Afterwards, the following fields are populated in the 'context' variable:
+17
View File
@@ -0,0 +1,17 @@
:sig
pid_t task_repeat(String key, f64 interval, std::function<void()> exec_func, u64 timeout = 60*10)
:params
key : string uniquely identifying the task
interval : repeat interval in seconds
exec_func : function to execute repeatedly
timeout : optional task timeout value
return value : the process ID of the started (or still running) task
:desc
Starts a repeating background worker process. The function `exec_func` is executed in a loop and the worker sleeps for `interval` seconds between executions.
If a process with the same `key` is already running, `task_repeat()` does not start a second worker and instead returns the PID of the existing one.
:see
>task
+17
View File
@@ -0,0 +1,17 @@
:sig
u64 ws_broadcast(String message, String scope = "")
:params
message : text message to send
scope : optional scope identifier, defaults to the current WebSocket page scope
return value : number of clients the message was queued for
:desc
Queues a text WebSocket message for every client connected to the given scope and returns the number of recipients.
If `scope` is omitted, the current page scope is used.
This helper currently sends text frames only.
:see
>websocket
+14
View File
@@ -0,0 +1,14 @@
:sig
bool ws_close(String connection_id = "")
:params
connection_id : optional target client ID, defaults to the current WebSocket client
return value : true if the target connection exists and was scheduled to close
:desc
Queues a WebSocket close frame and closes the targeted connection.
If `connection_id` is omitted, the current connection handled by `WS()` is closed.
:see
>websocket
+14
View File
@@ -0,0 +1,14 @@
:sig
u64 ws_connection_count(String scope = "")
:params
scope : optional scope identifier, defaults to the current WebSocket page scope
return value : number of connected WebSocket clients in that scope
:desc
Returns the number of currently connected WebSocket clients for the given scope.
If `scope` is omitted, the current page scope is used.
:see
>websocket
+13
View File
@@ -0,0 +1,13 @@
:sig
String ws_connection_id()
:params
return value : connection ID of the current WebSocket client
:desc
Returns the runtime-generated connection ID of the client whose message is currently being handled.
This ID can be passed to `ws_send_to()` or `ws_close()` to target a single connected client.
:see
>websocket
+14
View File
@@ -0,0 +1,14 @@
:sig
StringList ws_connections(String scope = "")
:params
scope : optional scope identifier, defaults to the current WebSocket page scope
return value : list of connection IDs currently connected to that scope
:desc
Returns the currently connected WebSocket client IDs for the given scope.
If `scope` is omitted, the current page scope is used.
:see
>websocket
+13
View File
@@ -0,0 +1,13 @@
:sig
bool ws_is_binary()
:params
return value : true if the current WebSocket message is binary
:desc
Returns whether the message currently being handled by `WS()` arrived as a binary frame.
If this returns `false`, the current message was delivered as a text frame.
:see
>websocket
+15
View File
@@ -0,0 +1,15 @@
:sig
String ws_message()
:params
return value : payload of the current WebSocket message
:desc
Returns the payload of the current WebSocket message being handled by `WS()`.
For text frames this is the decoded text payload. For binary frames this String contains the raw message bytes.
Use `ws_is_binary()` or `ws_opcode()` to decide how the payload should be interpreted.
:see
>websocket
+16
View File
@@ -0,0 +1,16 @@
:sig
u8 ws_opcode()
:params
return value : opcode of the current WebSocket message
:desc
Returns the opcode of the message currently being handled by `WS()`.
Common values are:
- `0x1` for text messages
- `0x2` for binary messages
:see
>websocket
+15
View File
@@ -0,0 +1,15 @@
:sig
String ws_scope()
:params
return value : scope identifier of the current WebSocket endpoint
:desc
Returns the runtime's scope identifier for the current WebSocket endpoint.
This is the same default scope used by `ws_send()`, `ws_broadcast()`, `ws_connections()`, and `ws_connection_count()` when no explicit scope is supplied.
In the current runtime implementation this scope is the page's internal endpoint identifier, typically the absolute `SCRIPT_FILENAME` of the `.ws.uce` file.
:see
>websocket
+17
View File
@@ -0,0 +1,17 @@
:sig
bool ws_send(String message, String scope = "")
:params
message : text message to send
scope : optional scope identifier, defaults to the current WebSocket page scope
return value : true if the message was queued for at least one connected client
:desc
Queues a text WebSocket message for every client connected to the given scope.
If `scope` is omitted, the current page scope is used.
This helper currently sends text frames only.
:see
>websocket
+15
View File
@@ -0,0 +1,15 @@
:sig
bool ws_send_to(String connection_id, String message)
:params
connection_id : ID of the target WebSocket client
message : text message to send
return value : true if the target connection exists and the message was queued
:desc
Queues a text WebSocket message for one specific connected client.
This helper currently sends text frames only.
:see
>websocket