Features
- RFC 6455 Compliant - Full WebSocket protocol support
- Frame Types - Text, Binary, Close, Ping, Pong, Continuation
- Masking - Client-side XOR masking per specification
- Fragmentation - Split large messages into multiple frames
- Handshake - Complete client/server handshake handling
- Streaming Parser - Process frames from byte streams
- Design by Contract - Full preconditions and postconditions
Quick Start
Installation
Add to your ECF:
<library name="simple_websocket" location="$SIMPLE_WEBSOCKET\simple_websocket.ecf"/>
Creating Frames
local
frame: WS_FRAME
bytes: ARRAY [NATURAL_8]
do
-- Text frame
create frame.make_text ("Hello, WebSocket!", True)
bytes := frame.to_bytes
-- Binary frame
create frame.make_binary (<<0x01, 0x02, 0x03>>, True)
-- Control frames
create frame.make_ping
create frame.make_pong
create frame.make_close (1000, "Normal closure")
end
Client Handshake
local
handshake: WS_HANDSHAKE
request: STRING
do
create handshake.make
-- Generate client handshake request
request := handshake.create_client_request ("example.com", "/chat")
-- Send request to server...
-- Validate server response
if handshake.validate_server_response (server_response) then
io.put_string ("WebSocket connection established!%N")
else
io.put_string ("Handshake failed: " + handshake.last_error + "%N")
end
end
Server Handshake
local
handshake: WS_HANDSHAKE
response: STRING
do
create handshake.make
-- Parse incoming client request
if handshake.parse_client_request (client_request) then
-- Generate server response
response := handshake.create_server_response
-- Send response to client...
else
-- Reject connection
io.put_string ("Invalid request: " + handshake.last_error + "%N")
end
end
Parsing Frames
local
parser: WS_FRAME_PARSER
bytes: ARRAY [NATURAL_8]
do
create parser.make
-- Add received bytes
parser.add_bytes (bytes)
-- Try to parse a complete frame
if parser.parse and parser.has_frame then
if attached parser.last_frame as frame then
if frame.is_text then
io.put_string ("Received: " + frame.text_payload + "%N")
elseif frame.is_close then
io.put_string ("Close code: " + frame.close_code.out + "%N")
end
end
end
end
Message Fragmentation
local
msg: WS_MESSAGE
frames: ARRAYED_LIST [WS_FRAME]
do
-- Create a large message
create msg.make_text ("Very long message content...")
-- Split into 1024-byte frames
frames := msg.to_frames (1024)
-- First frame has original opcode, rest are CONTINUATION
-- Last frame has FIN bit set
end
Frame Opcodes (RFC 6455)
| Opcode | Type | Description |
|---|---|---|
0x0 |
Continuation | Fragment continuation |
0x1 |
Text | UTF-8 text data |
0x2 |
Binary | Binary data |
0x8 |
Close | Connection close |
0x9 |
Ping | Heartbeat request |
0xA |
Pong | Heartbeat response |
Close Codes (RFC 6455)
| Code | Name | Description |
|---|---|---|
1000 |
Normal | Clean close |
1001 |
Going Away | Server shutting down |
1002 |
Protocol Error | Protocol violation |
1003 |
Unsupported | Unsupported data type |
1007 |
Invalid Payload | Malformed data |
1008 |
Policy Violation | Generic policy error |
1009 |
Message Too Big | Message exceeds limit |
1011 |
Server Error | Unexpected server error |
Frame Masking
Per RFC 6455, all client-to-server frames must be masked using XOR with a 4-byte key:
local
frame: WS_FRAME
mask: ARRAY [NATURAL_8]
do
create frame.make_text ("Hello", True)
-- Set 4-byte mask key (usually random)
mask := <<0x12, 0x34, 0x56, 0x78>>
frame.set_mask (mask)
-- to_bytes will now produce masked output
bytes := frame.to_bytes
end
The parser automatically unmasks incoming masked frames.