wsclient.lpr 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338
  1. program wsclient;
  2. {$mode objfpc}{$H+}
  3. uses
  4. {$IFDEF UNIX}
  5. cthreads,
  6. {$ENDIF}
  7. Classes, jsonparser, fpJSON,SysUtils, StrUtils, CustApp, uriparser, httpprotocol, fpwebsocketclient, fpwebsocket;
  8. type
  9. { TWebsocketClientApplication }
  10. TWebsocketClientApplication = class(TCustomApplication)
  11. private
  12. FUri : TUri;
  13. FLastRecipient : string;
  14. FAlias : String;
  15. FClient: TWebsocketClient;
  16. FPump : TWSMessagePump;
  17. FMsgCount : Integer;
  18. FUsePump : Boolean;
  19. procedure DoControl(Sender: TObject; aType: TFrameType; const aData: TBytes);
  20. procedure DoDisconnect(Sender: TObject);
  21. procedure DoIncomingMessage(Sender: TObject; const aMessage: TWSMessage);
  22. function SendMessage(const aTo, aLine: string): Boolean;
  23. procedure ShowHelp;
  24. Protected
  25. function AskAlias: String;
  26. function CheckMessages: boolean;
  27. function ConnectToServer: Boolean;
  28. function GetCommandOrMessage: Boolean;
  29. function ParseOptions: String;
  30. function QueryUser(Prompt: String; aDefault: String): String;
  31. procedure DoRun; override;
  32. public
  33. constructor Create(TheOwner: TComponent); override;
  34. destructor Destroy; override;
  35. procedure Usage(const aError: string); virtual;
  36. end;
  37. { TWebsocketClientApplication }
  38. function TWebsocketClientApplication.ParseOptions : String;
  39. begin
  40. if not HasOption('u','url') then
  41. Exit('Need URL option');
  42. FUri:=ParseURI(GetOptionValue('u','url'));
  43. if IndexText(FURI.Protocol,['ws','wss'])<0 then
  44. Exit('Invalid protocol in uri: need one of ws,wss');
  45. if (FURI.Port=0) then
  46. FURI.Port:=8080;
  47. FAlias:=GetOptionValue('a','alias');
  48. FUsePump:=HasOption('p','pump');
  49. end;
  50. Function TWebsocketClientApplication.QueryUser(Prompt : String; aDefault : String) : String;
  51. begin
  52. if aDefault<>'' then
  53. Prompt:=Prompt+' ['+aDefault+']';
  54. Write(Prompt+'> ');
  55. ReadLn(Result);
  56. if Result='' then
  57. Result:=aDefault;
  58. end;
  59. Function TWebsocketClientApplication.AskAlias : String;
  60. begin
  61. Repeat
  62. Result:=QueryUser('Please give your alias for the chat','');
  63. Until (Result<>'');
  64. end;
  65. procedure TWebsocketClientApplication.DoRun;
  66. var
  67. ErrorMsg: String;
  68. begin
  69. // quick check parameters
  70. ErrorMsg:=CheckOptions('hu:a:p', ['help','url','alias','pump']);
  71. if (ErrorMsg='') and not HasOption('h', 'help') then
  72. ErrorMsg:=ParseOptions;
  73. if (ErrorMsg<>'') or HasOption('h', 'help') then
  74. begin
  75. Usage(ErrorMsg);
  76. Terminate;
  77. Exit;
  78. end;
  79. if FAlias='' then
  80. FAlias:=AskAlias;
  81. if ConnectToServer then
  82. Writeln('Enter message or command (/stop /help), empty message will just check for incoming messages');
  83. SendMessage(FAlias,'Hello, this is a friendly greeting message from the client');
  84. CheckMessages;
  85. While not Terminated do
  86. begin
  87. GetCommandOrMessage;
  88. CheckMessages;
  89. end;
  90. Terminate;
  91. end;
  92. constructor TWebsocketClientApplication.Create(TheOwner: TComponent);
  93. begin
  94. inherited Create(TheOwner);
  95. StopOnException:=True;
  96. FClient:=TWebsocketClient.Create(Self);
  97. FClient.OnDisconnect:=@DoDisconnect;
  98. FClient.OnMessageReceived:=@DoIncomingMessage;
  99. FClient.OnControl:=@DoControl;
  100. end;
  101. destructor TWebsocketClientApplication.Destroy;
  102. begin
  103. FreeAndNil(FClient);
  104. inherited Destroy;
  105. end;
  106. procedure TWebsocketClientApplication.Usage(const aError : string);
  107. begin
  108. { add your help code here }
  109. writeln('Usage: ', ExeName, ' [options]');
  110. Writeln('where options is one or more of:');
  111. Writeln('-h --help this help text');
  112. Writeln('-u --url=URL the URL to connect to. Mandatory');
  113. Writeln('-a --alias=nick your nick name in the chat');
  114. Writeln('-p --pump use message pump');
  115. ExitCode:=Ord(aError<>'');
  116. end;
  117. Function TWebsocketClientApplication.ConnectToServer : Boolean;
  118. Var
  119. Res : string;
  120. begin
  121. FClient.HostName:=FURI.Host;
  122. FClient.Port:=FURI.Port;
  123. Res:=FURI.Path;
  124. if (FURI.Document<>'') then
  125. Res:=IncludeHTTPPathDelimiter(Res)+FURI.Document;
  126. FClient.Resource:=Res;
  127. if FUsePump then
  128. begin
  129. FPump:=TWSThreadMessagePump.Create(Self);
  130. FPump.Interval:=50;
  131. FClient.MessagePump:=FPump;
  132. FPump.Execute;
  133. end;
  134. try
  135. FClient.Connect;
  136. Result:=True;
  137. except
  138. on E : Exception do
  139. begin
  140. ShowException(E);
  141. terminate;
  142. end;
  143. end;
  144. end;
  145. Procedure TWebsocketClientApplication.ShowHelp;
  146. begin
  147. Writeln('Enter a command or a message text. Commands start with / and can be one of:');
  148. Writeln('/help - this text');
  149. Writeln('/quit - stop the program.');
  150. Writeln('/stop - stop the program.');
  151. Writeln('/ping [ping text] - send a ping.');
  152. Writeln('/pong [pong text] - send a pong.');
  153. end;
  154. Function TWebsocketClientApplication.GetCommandOrMessage : Boolean;
  155. Var
  156. aCmd,aLine,aTo : String;
  157. begin
  158. aLine:=QueryUser(FAlias,'');
  159. Result:=aLine<>'';
  160. if not Result then
  161. exit;
  162. if Copy(aLine,1,1)='/' then
  163. begin
  164. aCmd:=ExtractWord(1,aLine,[' ']);
  165. System.Delete(aLine,1,length(aCmd)+1);
  166. aCmd:=Copy(aCmd,2,Length(aCmd)-1);
  167. case lowercase(aCmd) of
  168. 'quit',
  169. 'stop' :
  170. begin
  171. Result:=False;
  172. Terminate;
  173. end;
  174. 'help':
  175. begin
  176. Result:=False;
  177. ShowHelp;
  178. end;
  179. 'ping':
  180. begin
  181. FClient.Ping(aLine);
  182. end;
  183. 'pong':
  184. begin
  185. FClient.Pong(aLine);
  186. end;
  187. end
  188. end
  189. else if (aLine<>'') then
  190. begin
  191. aTo:=QueryUser('Recipient',FLastRecipient);
  192. if (aTo<>'*') and (aTo<>'') then
  193. FLastRecipient:=aTo;
  194. if aTo='*' then
  195. aTo:='';
  196. SendMessage(aTo,aLine)
  197. end;
  198. end;
  199. Function TWebsocketClientApplication.SendMessage(const aTo,aLine : string) : Boolean;
  200. Var
  201. aJSON : TJSONObject;
  202. Msg : String;
  203. begin
  204. Result:=False;
  205. aJSON:=TJSONObject.Create(['from',FAlias,'msg',aLine,'to',aTo]);
  206. try
  207. Msg:=aJSON.asJSON;
  208. try
  209. FClient.SendMessage(msg);
  210. Result:=True;
  211. except
  212. on E : Exception do
  213. ShowException(E);
  214. end;
  215. finally
  216. aJSON.Free;
  217. end;
  218. end;
  219. procedure TWebsocketClientApplication.DoControl(Sender: TObject; aType: TFrameType; const aData: TBytes);
  220. var
  221. aReason : String;
  222. aCode : Integer;
  223. begin
  224. inc(fMsgCount);
  225. Case aType of
  226. ftClose:
  227. begin
  228. aCode:=TWSConnection(Sender).GetCloseData(aData,aReason);
  229. Writeln('Close code ',aCode,' received with readon: ',aReason);
  230. end;
  231. ftPing:
  232. begin
  233. Writeln('Ping received');
  234. end;
  235. ftPong:
  236. begin
  237. Writeln('Pong received');
  238. end;
  239. else
  240. Writeln('Unknown control code: ',aType);
  241. end;
  242. end;
  243. procedure TWebsocketClientApplication.DoDisconnect(Sender: TObject);
  244. begin
  245. Writeln('Connection closed, terminating');
  246. Terminate;
  247. end;
  248. procedure TWebsocketClientApplication.DoIncomingMessage(Sender: TObject; const aMessage: TWSMessage);
  249. Var
  250. S,From,Recip : String;
  251. D : TJSONData;
  252. Msg : TJSONObject absolute D;
  253. begin
  254. inc(fMsgCount);
  255. if not aMessage.IsText then
  256. begin
  257. Writeln('Incoming message is not text');
  258. exit;
  259. end;
  260. S:=aMessage.AsString;
  261. try
  262. D:=GetJSON(S);
  263. try
  264. if Not (D is TJSONOBject) then
  265. Raise EJSON.Create('Not an object: '+S);
  266. From:=Msg.Get('from','');
  267. Recip:=Msg.Get('to','');
  268. Write('From <',From,'>');
  269. if SameText(Recip,FAlias) then
  270. Writeln(' to you:')
  271. else
  272. Writeln(' to all:');
  273. Writeln(Msg.Get('msg',''));
  274. finally
  275. FreeAndNil(D)
  276. end;
  277. except
  278. Writeln('Incoming message is not valid JSON: ',S);
  279. end;
  280. end;
  281. Function TWebsocketClientApplication.CheckMessages: boolean;
  282. begin
  283. FMsgCount:=0;
  284. if FUsePump then
  285. CheckSynchronize()
  286. else
  287. while FClient.CheckIncoming=irOK do
  288. ;
  289. Result:=(FMsgCount>0);
  290. end;
  291. var
  292. Application: TWebsocketClientApplication;
  293. begin
  294. Application:=TWebsocketClientApplication.Create(nil);
  295. Application.Title:='Websocket Client Application';
  296. Application.Run;
  297. Application.Free;
  298. end.