Commit 8ffcf8f3 by Mac Stephens

Add WebSocket client tracking, connection management, and targeted test messaging

parent 8ec307c3
...@@ -46,7 +46,7 @@ object FMain: TFMain ...@@ -46,7 +46,7 @@ object FMain: TFMain
Left = 0 Left = 0
Top = 0 Top = 0
Width = 748 Width = 748
Height = 547 Height = 487
Align = alClient Align = alClient
DataSource = dsConnectedClients DataSource = dsConnectedClients
Options = [dgTitles, dgIndicator, dgColumnResize, dgColLines, dgRowLines, dgTabs, dgRowSelect, dgConfirmDelete, dgCancelOnExit, dgTitleClick, dgTitleHotTrack] Options = [dgTitles, dgIndicator, dgColumnResize, dgColLines, dgRowLines, dgTabs, dgRowSelect, dgConfirmDelete, dgCancelOnExit, dgTitleClick, dgTitleHotTrack]
...@@ -57,6 +57,59 @@ object FMain: TFMain ...@@ -57,6 +57,59 @@ object FMain: TFMain
TitleFont.Height = -11 TitleFont.Height = -11
TitleFont.Name = 'Tahoma' TitleFont.Name = 'Tahoma'
TitleFont.Style = [] TitleFont.Style = []
Columns = <
item
Expanded = False
FieldName = 'ConnectionId'
Visible = True
end
item
Expanded = False
FieldName = 'UserId'
Visible = True
end
item
Expanded = False
FieldName = 'ConnectedAt'
Width = 150
Visible = True
end>
end
object pnlConnectedClientsActions: TPanel
Left = 0
Top = 487
Width = 748
Height = 60
Align = alBottom
Caption = 'pnlConnectedClientsActions'
ShowCaption = False
TabOrder = 1
ExplicitTop = 467
object btnDisconnectClient: TButton
Left = 5
Top = 18
Width = 141
Height = 25
Caption = 'Disconnect Selected Client'
TabOrder = 0
OnClick = btnDisconnectClientClick
end
object edtClientMessage: TEdit
Left = 294
Top = 12
Width = 317
Height = 38
TabOrder = 1
end
object btnSendClientMessage: TButton
Left = 617
Top = 18
Width = 123
Height = 25
Caption = 'Send Client Message'
TabOrder = 2
OnClick = btnSendClientMessageClick
end
end end
end end
end end
...@@ -89,13 +142,13 @@ object FMain: TFMain ...@@ -89,13 +142,13 @@ object FMain: TFMain
end end
object initTimer: TTimer object initTimer: TTimer
OnTimer = initTimerTimer OnTimer = initTimerTimer
Left = 30 Left = 448
Top = 466 Top = 4
end end
object ExeInfo1: TExeInfo object ExeInfo1: TExeInfo
Version = '1.6.1.1' Version = '1.6.1.1'
Left = 32 Left = 572
Top = 530 Top = 4
end end
object tblConnectedClients: TFDMemTable object tblConnectedClients: TFDMemTable
Active = True Active = True
...@@ -103,12 +156,12 @@ object FMain: TFMain ...@@ -103,12 +156,12 @@ object FMain: TFMain
item item
Name = 'ConnectionId' Name = 'ConnectionId'
DataType = ftString DataType = ftString
Size = 20 Size = 50
end end
item item
Name = 'UserId' Name = 'UserId'
DataType = ftString DataType = ftString
Size = 20 Size = 30
end end
item item
Name = 'ConnectedAt' Name = 'ConnectedAt'
...@@ -123,12 +176,12 @@ object FMain: TFMain ...@@ -123,12 +176,12 @@ object FMain: TFMain
UpdateOptions.CheckRequired = False UpdateOptions.CheckRequired = False
UpdateOptions.AutoCommitUpdates = True UpdateOptions.AutoCommitUpdates = True
StoreDefs = True StoreDefs = True
Left = 134 Left = 514
Top = 473 Top = 3
end end
object dsConnectedClients: TDataSource object dsConnectedClients: TDataSource
DataSet = tblConnectedClients DataSet = tblConnectedClients
Left = 136 Left = 378
Top = 527 Top = 7
end end
end end
...@@ -7,9 +7,7 @@ uses ...@@ -7,9 +7,7 @@ uses
System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs,
Vcl.StdCtrls, Vcl.ExtCtrls, System.Generics.Collections, System.IniFiles, Vcl.StdCtrls, Vcl.ExtCtrls, System.Generics.Collections, System.IniFiles,
Auth.Service, Auth.Server.Module, Api.Server.Module, App.Server.Module, Auth.Service, Auth.Server.Module, Api.Server.Module, App.Server.Module,
ExeInfo, Api.Service, Vcl.ComCtrls, WebSocket.Manager, ExeInfo, Api.Service, Vcl.ComCtrls, WebSocket.Manager, FireDAC.Stan.Intf,
VCL.TMSFNCWebSocketCommon, VCL.TMSFNCCustomComponent,
VCL.TMSFNCWebSocketClient, WEBLib.WebSocketClient, FireDAC.Stan.Intf,
FireDAC.Stan.Option, FireDAC.Stan.Param, FireDAC.Stan.Error, FireDAC.DatS, FireDAC.Stan.Option, FireDAC.Stan.Param, FireDAC.Stan.Error, FireDAC.DatS,
FireDAC.Phys.Intf, FireDAC.DApt.Intf, Data.DB, Vcl.Grids, Vcl.DBGrids, FireDAC.Phys.Intf, FireDAC.DApt.Intf, Data.DB, Vcl.Grids, Vcl.DBGrids,
FireDAC.Comp.DataSet, FireDAC.Comp.Client; FireDAC.Comp.DataSet, FireDAC.Comp.Client;
...@@ -28,16 +26,24 @@ type ...@@ -28,16 +26,24 @@ type
tblConnectedClients: TFDMemTable; tblConnectedClients: TFDMemTable;
dsConnectedClients: TDataSource; dsConnectedClients: TDataSource;
grdConnectedClients: TDBGrid; grdConnectedClients: TDBGrid;
pnlConnectedClientsActions: TPanel;
btnDisconnectClient: TButton;
edtClientMessage: TEdit;
btnSendClientMessage: TButton;
procedure btnApiSwaggerUIClick(Sender: TObject); procedure btnApiSwaggerUIClick(Sender: TObject);
procedure btnExitClick(Sender: TObject); procedure btnExitClick(Sender: TObject);
procedure ContactFormData(AText: String); procedure ContactFormData(AText: String);
procedure FormClose(Sender: TObject; var Action: TCloseAction); procedure FormClose(Sender: TObject; var Action: TCloseAction);
procedure initTimerTimer(Sender: TObject); procedure initTimerTimer(Sender: TObject);
procedure btnAuthSwaggerUIClick(Sender: TObject); procedure btnAuthSwaggerUIClick(Sender: TObject);
procedure btnDisconnectClientClick(Sender: TObject);
procedure btnSendClientMessageClick(Sender: TObject);
strict private strict private
FWebSocketManager: TWebSocketManager; FWebSocketManager: TWebSocketManager;
procedure StartServers; procedure StartServers;
function LogValue(const LabelName: string; const Value: string; FromIni: Boolean): string; function LogValue(const LabelName: string; const Value: string; FromIni: Boolean): string;
procedure HandleConnectedClientsChanged;
procedure RefreshConnectedClients;
private private
function LocalBrowserUrl(const Url: string): string; function LocalBrowserUrl(const Url: string): string;
end; end;
...@@ -64,6 +70,33 @@ begin ...@@ -64,6 +70,33 @@ begin
Close; Close;
end; end;
procedure TFMain.btnSendClientMessageClick(Sender: TObject);
var
connectionId: string;
begin
if tblConnectedClients.IsEmpty then
Exit;
if Trim(edtClientMessage.Text) = '' then
Exit;
connectionId := tblConnectedClients.FieldByName('ConnectionId').AsString;
FWebSocketManager.SendMessageToClient(connectionId, edtClientMessage.Text);
end;
procedure TFMain.btnDisconnectClientClick(Sender: TObject);
var
connectionId: string;
begin
if tblConnectedClients.IsEmpty then
Exit;
connectionId := tblConnectedClients.FieldByName('ConnectionId').AsString;
FWebSocketManager.DisconnectClient(connectionId);
end;
function TFMain.LocalBrowserUrl(const Url: string): string; function TFMain.LocalBrowserUrl(const Url: string): string;
begin begin
Result := StringReplace(Url, '://0.0.0.0:', '://localhost:', [rfIgnoreCase]); Result := StringReplace(Url, '://0.0.0.0:', '://localhost:', [rfIgnoreCase]);
...@@ -105,8 +138,11 @@ end; ...@@ -105,8 +138,11 @@ end;
procedure TFMain.FormClose(Sender: TObject; var Action: TCloseAction); procedure TFMain.FormClose(Sender: TObject; var Action: TCloseAction);
begin begin
FWebSocketManager.OnClientsChanged := nil; if Assigned(FWebSocketManager) then
FWebSocketManager.Free; begin
FWebSocketManager.OnClientsChanged := nil;
FreeAndNil(FWebSocketManager);
end;
ServerConfig.Free; ServerConfig.Free;
IniEntries.Free; IniEntries.Free;
...@@ -155,6 +191,7 @@ begin ...@@ -155,6 +191,7 @@ begin
AppServerModule.StartAppServer(ServerConfig.url); AppServerModule.StartAppServer(ServerConfig.url);
FWebSocketManager := TWebSocketManager.Create; FWebSocketManager := TWebSocketManager.Create;
FWebSocketManager.OnClientsChanged := HandleConnectedClientsChanged;
FWebSocketManager.Start; FWebSocketManager.Start;
Logger.Log(1, 'WebSocket server started on port 8091'); Logger.Log(1, 'WebSocket server started on port 8091');
except except
...@@ -163,6 +200,39 @@ begin ...@@ -163,6 +200,39 @@ begin
end; end;
end; end;
procedure TFMain.HandleConnectedClientsChanged;
begin
TThread.Queue(nil,
procedure
begin
RefreshConnectedClients;
end);
end;
procedure TFMain.RefreshConnectedClients;
var
clients: TArray<TConnectedClientSnapshot>;
client: TConnectedClientSnapshot;
begin
clients := FWebSocketManager.GetClientSnapshots;
tblConnectedClients.DisableControls;
try
tblConnectedClients.EmptyDataSet;
for client in clients do
begin
tblConnectedClients.Append;
tblConnectedClients.FieldByName('ConnectionId').AsString := client.ConnectionId;
tblConnectedClients.FieldByName('UserId').AsString := client.UserId;
tblConnectedClients.FieldByName('ConnectedAt').AsDateTime := client.ConnectedAt;
tblConnectedClients.Post;
end;
finally
tblConnectedClients.EnableControls;
end;
end;
end. end.
...@@ -27,8 +27,7 @@ type ...@@ -27,8 +27,7 @@ type
property ConnectionId: string read FConnectionId write FConnectionId; property ConnectionId: string read FConnectionId write FConnectionId;
property UserId: string read FUserId write FUserId; property UserId: string read FUserId write FUserId;
property ConnectedAt: TDateTime read FConnectedAt write FConnectedAt; property ConnectedAt: TDateTime read FConnectedAt write FConnectedAt;
property Connection: TTMSFNCWebSocketServerConnection property Connection: TTMSFNCWebSocketServerConnection read FConnection write FConnection;
read FConnection write FConnection;
end; end;
TClientsChangedEvent = procedure of object; TClientsChangedEvent = procedure of object;
...@@ -41,22 +40,20 @@ type ...@@ -41,22 +40,20 @@ type
FOnClientsChanged: TClientsChangedEvent; FOnClientsChanged: TClientsChangedEvent;
procedure NotifyClientsChanged; procedure NotifyClientsChanged;
procedure HandshakeResponseSent(Sender: TObject; procedure HandshakeResponseSent(Sender: TObject; AConnection: TTMSFNCWebSocketServerConnection);
AConnection: TTMSFNCWebSocketServerConnection); procedure MessageReceived(Sender: TObject; AConnection: TTMSFNCWebSocketConnection; const AMessage: string);
procedure MessageReceived(Sender: TObject; procedure ClientDisconnected(Sender: TObject; AConnection: TTMSFNCWebSocketConnection);
AConnection: TTMSFNCWebSocketConnection; const AMessage: string);
procedure ClientDisconnected(Sender: TObject;
AConnection: TTMSFNCWebSocketConnection);
public public
constructor Create; constructor Create;
destructor Destroy; override; destructor Destroy; override;
procedure Start; procedure Start;
procedure Stop; procedure Stop;
procedure DisconnectClient(const AConnectionId: string);
procedure SendMessageToClient(const AConnectionId, AText: string);
function GetClientSnapshots: TArray<TConnectedClientSnapshot>; function GetClientSnapshots: TArray<TConnectedClientSnapshot>;
property OnClientsChanged: TClientsChangedEvent property OnClientsChanged: TClientsChangedEvent read FOnClientsChanged write FOnClientsChanged;
read FOnClientsChanged write FOnClientsChanged;
end; end;
implementation implementation
...@@ -102,14 +99,73 @@ begin ...@@ -102,14 +99,73 @@ begin
FServer.Active := False; FServer.Active := False;
end; end;
procedure TWebSocketManager.DisconnectClient(const AConnectionId: string);
var
client: TConnectedClient;
connection: TTMSFNCWebSocketServerConnection;
begin
connection := nil;
TMonitor.Enter(FClientsLock);
try
for client in FClients do
begin
if SameText(client.ConnectionId, AConnectionId) then
begin
connection := client.Connection;
Break;
end;
end;
finally
TMonitor.Exit(FClientsLock);
end;
if Assigned(connection) then
connection.SendClose;
end;
procedure TWebSocketManager.SendMessageToClient(const AConnectionId, AText: string);
var
client: TConnectedClient;
connection: TTMSFNCWebSocketServerConnection;
json: TJSONObject;
begin
connection := nil;
TMonitor.Enter(FClientsLock);
try
for client in FClients do
begin
if SameText(client.ConnectionId, AConnectionId) then
begin
connection := client.Connection;
Break;
end;
end;
finally
TMonitor.Exit(FClientsLock);
end;
if not Assigned(connection) then
Exit;
json := TJSONObject.Create;
try
json.AddPair('message', 'test_message');
json.AddPair('text', AText);
connection.Send(json.ToJSON);
finally
json.Free;
end;
end;
procedure TWebSocketManager.NotifyClientsChanged; procedure TWebSocketManager.NotifyClientsChanged;
begin begin
if Assigned(FOnClientsChanged) then if Assigned(FOnClientsChanged) then
FOnClientsChanged; FOnClientsChanged;
end; end;
procedure TWebSocketManager.HandshakeResponseSent(Sender: TObject; procedure TWebSocketManager.HandshakeResponseSent(Sender: TObject; AConnection: TTMSFNCWebSocketServerConnection);
AConnection: TTMSFNCWebSocketServerConnection);
var var
client: TConnectedClient; client: TConnectedClient;
guid: TGUID; guid: TGUID;
...@@ -135,8 +191,7 @@ begin ...@@ -135,8 +191,7 @@ begin
NotifyClientsChanged; NotifyClientsChanged;
end; end;
procedure TWebSocketManager.MessageReceived(Sender: TObject; procedure TWebSocketManager.MessageReceived(Sender: TObject; AConnection: TTMSFNCWebSocketConnection; const AMessage: string);
AConnection: TTMSFNCWebSocketConnection; const AMessage: string);
var var
json: TJSONValue; json: TJSONValue;
messageType: string; messageType: string;
...@@ -158,9 +213,7 @@ begin ...@@ -158,9 +213,7 @@ begin
if not json.TryGetValue<string>('userId', userId) then if not json.TryGetValue<string>('userId', userId) then
Exit; Exit;
client := TConnectedClient( client := TConnectedClient(TTMSFNCWebSocketServerConnection(AConnection).UserData);
TTMSFNCWebSocketServerConnection(AConnection).UserData
);
if not Assigned(client) then if not Assigned(client) then
Exit; Exit;
...@@ -176,17 +229,14 @@ begin ...@@ -176,17 +229,14 @@ begin
TMonitor.Exit(FClientsLock); TMonitor.Exit(FClientsLock);
end; end;
Logger.Log(1, 'WebSocket client identified: ' + Logger.Log(1, 'WebSocket client identified: ' + connectionId + ' - ' + userId);
connectionId + ' - ' + userId);
NotifyClientsChanged; NotifyClientsChanged;
finally finally
json.Free; json.Free;
end; end;
end; end;
procedure TWebSocketManager.ClientDisconnected(Sender: TObject; procedure TWebSocketManager.ClientDisconnected(Sender: TObject; AConnection: TTMSFNCWebSocketConnection);
AConnection: TTMSFNCWebSocketConnection);
var var
serverConnection: TTMSFNCWebSocketServerConnection; serverConnection: TTMSFNCWebSocketServerConnection;
client: TConnectedClient; client: TConnectedClient;
...@@ -210,14 +260,11 @@ begin ...@@ -210,14 +260,11 @@ begin
TMonitor.Exit(FClientsLock); TMonitor.Exit(FClientsLock);
end; end;
Logger.Log(1, 'WebSocket client disconnected: ' + Logger.Log(1, 'WebSocket client disconnected: ' + connectionId + ' - ' + userId);
connectionId + ' - ' + userId);
NotifyClientsChanged; NotifyClientsChanged;
end; end;
function TWebSocketManager.GetClientSnapshots: function TWebSocketManager.GetClientSnapshots: TArray<TConnectedClientSnapshot>;
TArray<TConnectedClientSnapshot>;
var var
i: Integer; i: Integer;
begin begin
......
[Settings] [Settings]
LogFileNum=738 LogFileNum=743
webClientVersion=9.4.0 webClientVersion=9.4.0
[Database] [Database]
......
...@@ -176,10 +176,20 @@ begin ...@@ -176,10 +176,20 @@ begin
wsClient.Send(TJSJSON.stringify(msg)); wsClient.Send(TJSJSON.stringify(msg));
end; end;
procedure TFViewMain.wsClientDataReceived(Sender: TObject; Origin: string; procedure TFViewMain.wsClientDataReceived(Sender: TObject; Origin: string; SocketData: TJSObjectRecord);
SocketData: TJSObjectRecord); var
messageObj: TJSObject;
messageType: string;
messageText: string;
begin begin
console.log('WebSocket message received: ' + SocketData.jsObject.toString); messageObj := TJSObject(TJSJSON.parse(SocketData.jsobject.toString));
messageType := string(messageObj['message']);
if messageType = 'test_message' then
begin
messageText := string(messageObj['text']);
window.alert(messageText);
end;
end; end;
procedure TFViewMain.wsClientDisconnect(Sender: TObject); procedure TFViewMain.wsClientDisconnect(Sender: TObject);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment