Custom connection data
A server that only broadcasts can treat its clients as anonymous, but most real
servers need to know who is on each connection: a user ID, a display name, the
room a user joined, the topics a device subscribed to, or a timestamp of the
last activity. TTMSFNCWebSocketServerConnection carries a UserData object
for exactly that, with optional automatic cleanup when the client leaves. This
chapter covers attaching data to a connection, the two usual moments to do it —
when the client tells you who it is, or when the server assigns an identity
itself — and how that data turns into message routing. Use it whenever a
message handler or a send selector needs to tell one client from another.
Attaching data to a client
UserData holds any
TObject you assign; define a small class with the fields you need. Set
OwnsUserData
to True and the connection frees the object when it is destroyed after the
client disconnects. With OwnsUserData at False you own the object and must
free it yourself.
In event handlers that pass a TTMSFNCWebSocketConnection (the message, ping,
pong, and close events), cast AConnection to
TTMSFNCWebSocketServerConnection
to reach UserData; OnAllow and OnHandshakeResponseSent already pass the
server connection type.
Storing data the client sends
When the data comes from the client itself — a login, a registration, a room to join — the message handler is the place to store it. The example accepts a JSON registration message and keeps the client's ID on its connection.
uses
System.JSON;
type
TMyDataObject = class
private
FID: string;
public
property ID: string read FID write FID;
end;
procedure TForm1.TMSFNCWebSocketServer1MessageReceived(Sender: TObject;
AConnection: TTMSFNCWebSocketConnection; const AMessage: string);
var
Json: TJSONValue;
Conn: TTMSFNCWebSocketServerConnection;
MsgType, ID: string;
begin
// On the server, every connection is a TTMSFNCWebSocketServerConnection.
Conn := AConnection as TTMSFNCWebSocketServerConnection;
Json := TJSONObject.ParseJSONValue(AMessage);
if not Assigned(Json) then
Exit;
try
if not Json.TryGetValue<string>('type', MsgType) then
Exit;
// {"type":"user-data","id":"42"}
if (MsgType = 'user-data') and not Assigned(Conn.UserData) then
begin
Json.TryGetValue<string>('id', ID);
Conn.OwnsUserData := True; // freed together with the connection
Conn.UserData := TMyDataObject.Create;
TMyDataObject(Conn.UserData).ID := ID;
end
else if MsgType = 'join' then
begin
// handle other message types here
end;
finally
Json.Free;
end;
end;
Assigning data when the connection opens
When the server assigns the data — a session ID, a sequence number, a default
room — assign it in
OnHandshakeResponseSent,
which fires when the server has answered the handshake and the connection is
established. Every client then has data from the start, and message handlers
never meet a connection without it.
procedure TForm1.TMSFNCWebSocketServer1HandshakeResponseSent(Sender: TObject;
AConnection: TTMSFNCWebSocketServerConnection);
begin
// The connection is established: attach data the server owns.
AConnection.OwnsUserData := True;
AConnection.UserData := TMyDataObject.Create;
TMyDataObject(AConnection.UserData).ID := TGUID.NewGuid.ToString;
AConnection.Send(Format('{"type":"your-id","id":"%s"}',
[TMyDataObject(AConnection.UserData).ID]));
end;
Data that arrives with the handshake itself — a token in the query string, a
cookie in the headers — can be read from
HandshakeRequest
in the same event.
Combining assigned IDs with message routing
Connection data pays off in send selectors. The example delivers a direct message by looking up the recipient among the IDs assigned when each connection opened.
uses
System.JSON;
procedure TForm1.TMSFNCWebSocketServer1MessageReceived(Sender: TObject;
AConnection: TTMSFNCWebSocketConnection; const AMessage: string);
var
Json: TJSONValue;
TargetID, Text, SenderID: string;
begin
// {"type":"direct","to":"<id>","text":"Hi"}
Json := TJSONObject.ParseJSONValue(AMessage);
if not Assigned(Json) then
Exit;
try
if Json.TryGetValue<string>('to', TargetID) and Json.TryGetValue<string>('text', Text) then
begin
// The IDs were assigned in OnHandshakeResponseSent.
SenderID := TMyDataObject((AConnection as TTMSFNCWebSocketServerConnection).UserData).ID;
TMSFNCWebSocketServer1.SendMessageTo(
Format('{"type":"direct","from":"%s","text":"%s"}', [SenderID, Text]),
function(AClient: TTMSFNCWebSocketServerConnection): Boolean
begin
Result := Assigned(AClient.UserData) and
(TMyDataObject(AClient.UserData).ID = TargetID);
end);
end;
finally
Json.Free;
end;
end;
Pitfalls
- Decide who frees the object. Set
OwnsUserData := Truefor data that lives exactly as long as the connection; otherwise free it yourself, or it leaks. - Check before casting. A connection has no
UserDatauntil you assign it; testAssigned(UserData)in handlers and selectors that can run earlier. - Data is freed with the connection. With
OwnsUserData, the object is gone once the client has disconnected; copy anything you need to keep while the client is still connected. - Mind the threads. Without
AutoSyncEvents, handlers for different clients run concurrently; shared structures that several connections update need locking.
Related API
TTMSFNCWebSocketServerConnection,UserData,OwnsUserData,HandshakeRequestOnHandshakeResponseSent,OnMessageReceived,SendMessageTo
See also
- Broadcasting messages — route messages with selectors.
- Running a server — the connection lifecycle and event threads.
- Connection health — track client liveness in connection data.