wschat.pp 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  1. {
  2. $Id: header,v 1.1 2000/07/13 06:33:45 michael Exp $
  3. This file is part of the Free Component Library (FCL)
  4. Copyright (c) 2021 - by the Free Pascal development team
  5. Simple websocket chat server implementation
  6. See the file COPYING.FPC, included in this distribution,
  7. for details about the copyright.
  8. This program is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  11. **********************************************************************}
  12. unit wschat;
  13. {$mode ObjFPC}{$H+}
  14. interface
  15. uses
  16. Classes, SysUtils, fpcustwsserver, fpwebsocket, syncobjs, fpjson;
  17. Type
  18. { TWebsocketChat }
  19. TChatLogEvent = procedure (Sender : TObject; Const Msg : String) of object;
  20. TWebsocketChat = Class(TComponent)
  21. Private
  22. FLock : TCriticalSection;
  23. FMap : TStringList;
  24. FOnLog: TChatLogEvent;
  25. FSrv: TCustomWSServer;
  26. procedure SetServer(AValue: TCustomWSServer);
  27. Protected
  28. Procedure DoLog(Const Msg : String); overload;
  29. Procedure DoLog(Const Fmt : String; Args : Array of const); overload;
  30. procedure Notification(AComponent: TComponent; Operation: TOperation); override;
  31. function GetConnectionFromUser(aFrom: String): TWSConnection; virtual;
  32. procedure MapConnection(aFrom: String; aConn: TWSConnection); virtual;
  33. Public
  34. Constructor Create(aOwner : TComponent); override;
  35. Destructor Destroy; override;
  36. procedure DoControlReceived(Sender: TObject; aType: TFrameType; const aData: TBytes); virtual;
  37. procedure DoDisconnect(Sender: TObject); virtual;
  38. procedure DoMessageReceived(Sender: TObject; const aMessage: TWSMessage); virtual;
  39. Property WebsocketServer : TCustomWSServer Read FSrv Write SetServer;
  40. Property OnLog : TChatLogEvent Read FOnLog Write FOnLog;
  41. end;
  42. implementation
  43. Constructor TWebsocketChat.Create(aOwner : TComponent);
  44. begin
  45. Inherited;
  46. FMap:=TStringList.Create;
  47. FLock:=TCriticalSection.Create;
  48. end;
  49. destructor TWebsocketChat.Destroy;
  50. begin
  51. FreeAndNil(FLock);
  52. FreeAndNil(FMap);
  53. inherited Destroy;
  54. end;
  55. procedure TWebsocketChat.DoMessageReceived(Sender: TObject; const aMessage: TWSMessage);
  56. Var
  57. S,From,Recip : String;
  58. D : TJSONData;
  59. Msg : TJSONObject absolute D;
  60. SenderConn,RecipConn : TWSConnection;
  61. begin
  62. SenderConn:=Sender as TWSConnection;
  63. RecipConn:=Nil;
  64. S:=aMessage.AsString;
  65. DoLog('Received message: '+S);
  66. try
  67. D:=GetJSON(S);
  68. try
  69. if Not (D is TJSONOBject) then
  70. Raise EJSON.Create('Not an object: '+S);
  71. From:=Msg.Get('from','');
  72. if From<>'' then
  73. MapConnection(From,SenderConn);
  74. Recip:=Msg.Get('to','');
  75. finally
  76. FreeAndNil(D)
  77. end;
  78. except
  79. DoLog('Message is not JSON, echoing as JSON');
  80. S:='{ "msg": "You sent: '+StringReplace(S,'"','\"',[rfReplaceAll])+'" }';
  81. RecipConn:=SenderConn;
  82. end;
  83. if (Recip<>'') then
  84. begin
  85. RecipConn:=GetConnectionFromUser(Recip);
  86. if RecipConn=Nil then
  87. exit;
  88. end;
  89. if Assigned(RecipConn) then
  90. RecipConn.Send(S)
  91. else
  92. FSRv.BroadcastMessage(S);
  93. end;
  94. procedure TWebsocketChat.DoControlReceived(Sender: TObject; aType: TFrameType; const aData: TBytes);
  95. Var
  96. aReason : String;
  97. aCode : Integer;
  98. begin
  99. Case aType of
  100. ftClose:
  101. begin
  102. aCode:=TWSConnection(Sender).GetCloseData(aData,aReason);
  103. DoLog('Close code %d received with reason: %s',[aCode,aReason]);
  104. end;
  105. ftPing:
  106. begin
  107. DoLog('Ping received');
  108. end;
  109. ftPong:
  110. begin
  111. DoLog('Pong received');
  112. end;
  113. else
  114. DoLog('Unknown control code: %d',[Ord(aType)]);
  115. end;
  116. end;
  117. procedure TWebsocketChat.DoDisconnect(Sender: TObject);
  118. Var
  119. Conn : TWSConnection;
  120. Found : Boolean;
  121. I : Integer;
  122. aID,N,V : String;
  123. begin
  124. Conn:=(Sender as TWSConnection);
  125. aID:=Conn.ConnectionID;
  126. DoLog('Connection '+aID+' disappeared');
  127. FLock.Enter;
  128. try
  129. Found:=False;
  130. I:=FMap.Count-1;
  131. While (I>=0) and not Found do
  132. begin
  133. FMap.GetNameValue(I,N,V);
  134. Found:=SameText(V,aID);
  135. if Found then
  136. FMap.Delete(I);
  137. Dec(I);
  138. end;
  139. finally
  140. Flock.Leave;
  141. end;
  142. end;
  143. Function TWebsocketChat.GetConnectionFromUser(aFrom : String): TWSConnection;
  144. Var
  145. aID : String;
  146. begin
  147. FLock.Enter;
  148. try
  149. aID:=FMap.Values[aFrom];
  150. finally
  151. FLock.Leave;
  152. end;
  153. Result:=FSrv.Connections.FindConnectionById(aID);
  154. end;
  155. procedure TWebsocketChat.MapConnection(aFrom : String; aConn : TWSConnection);
  156. begin
  157. // We could also store the connection object directly in the objects array,
  158. // but this way we demonstrate the ConnectionID and FindConnectionByID
  159. Flock.Enter;
  160. try
  161. FMap.Values[aFrom]:=aConn.ConnectionID;
  162. finally
  163. FLock.Leave;
  164. end;
  165. end;
  166. procedure TWebsocketChat.SetServer(AValue: TCustomWSServer);
  167. begin
  168. if FSrv=AValue then Exit;
  169. if Assigned(FSRV) then
  170. FSRV.RemoveFreeNotification(Self);
  171. FSrv:=AValue;
  172. if Assigned(FSRV) then
  173. FSRV.FreeNotification(Self);
  174. end;
  175. procedure TWebsocketChat.DoLog(const Msg: String);
  176. begin
  177. If Assigned(FonLog) then
  178. FOnLog(Self,Msg);
  179. end;
  180. procedure TWebsocketChat.DoLog(const Fmt: String; Args: array of const);
  181. begin
  182. DoLog(Format(Fmt,Args));
  183. end;
  184. procedure TWebsocketChat.Notification(AComponent: TComponent; Operation: TOperation);
  185. begin
  186. inherited Notification(AComponent, Operation);
  187. if (Operation=opRemove) and (aComponent=FSrv) then
  188. FSrv:=Nil;
  189. end;
  190. end.