wsclient.lpr 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339
  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. if aError<>'' then
  109. Writeln('Error : ',aError);
  110. writeln('Usage: ', ExeName, ' [options]');
  111. Writeln('where options is one or more of:');
  112. Writeln('-h --help this help text');
  113. Writeln('-u --url=URL the URL to connect to. Mandatory');
  114. Writeln('-a --alias=nick your nick name in the chat');
  115. Writeln('-p --pump use message pump');
  116. ExitCode:=Ord(aError<>'');
  117. end;
  118. Function TWebsocketClientApplication.ConnectToServer : Boolean;
  119. Var
  120. Res : string;
  121. begin
  122. FClient.HostName:=FURI.Host;
  123. FClient.Port:=FURI.Port;
  124. Res:=FURI.Path;
  125. if (FURI.Document<>'') then
  126. Res:=IncludeHTTPPathDelimiter(Res)+FURI.Document;
  127. FClient.Resource:=Res;
  128. if FUsePump then
  129. begin
  130. FPump:=TWSThreadMessagePump.Create(Self);
  131. FPump.Interval:=50;
  132. FClient.MessagePump:=FPump;
  133. FPump.Execute;
  134. end;
  135. try
  136. FClient.Connect;
  137. Result:=True;
  138. except
  139. on E : Exception do
  140. begin
  141. ShowException(E);
  142. terminate;
  143. end;
  144. end;
  145. end;
  146. Procedure TWebsocketClientApplication.ShowHelp;
  147. begin
  148. Writeln('Enter a command or a message text. Commands start with / and can be one of:');
  149. Writeln('/help - this text');
  150. Writeln('/quit - stop the program.');
  151. Writeln('/stop - stop the program.');
  152. Writeln('/ping [ping text] - send a ping.');
  153. Writeln('/pong [pong text] - send a pong.');
  154. end;
  155. Function TWebsocketClientApplication.GetCommandOrMessage : Boolean;
  156. Var
  157. aCmd,aLine,aTo : String;
  158. begin
  159. aLine:=QueryUser(FAlias,'');
  160. Result:=aLine<>'';
  161. if not Result then
  162. exit;
  163. if Copy(aLine,1,1)='/' then
  164. begin
  165. aCmd:=ExtractWord(1,aLine,[' ']);
  166. System.Delete(aLine,1,length(aCmd)+1);
  167. aCmd:=Copy(aCmd,2,Length(aCmd)-1);
  168. case lowercase(aCmd) of
  169. 'quit',
  170. 'stop' :
  171. begin
  172. Result:=False;
  173. Terminate;
  174. end;
  175. 'help':
  176. begin
  177. Result:=False;
  178. ShowHelp;
  179. end;
  180. 'ping':
  181. begin
  182. FClient.Ping(aLine);
  183. end;
  184. 'pong':
  185. begin
  186. FClient.Pong(aLine);
  187. end;
  188. end
  189. end
  190. else if (aLine<>'') then
  191. begin
  192. aTo:=QueryUser('Recipient',FLastRecipient);
  193. if (aTo<>'*') and (aTo<>'') then
  194. FLastRecipient:=aTo;
  195. if aTo='*' then
  196. aTo:='';
  197. SendMessage(aTo,aLine)
  198. end;
  199. end;
  200. Function TWebsocketClientApplication.SendMessage(const aTo,aLine : string) : Boolean;
  201. Var
  202. aJSON : TJSONObject;
  203. Msg : String;
  204. begin
  205. Result:=False;
  206. aJSON:=TJSONObject.Create(['from',FAlias,'msg',aLine,'to',aTo]);
  207. try
  208. Msg:=aJSON.asJSON;
  209. try
  210. FClient.SendMessage(msg);
  211. Result:=True;
  212. except
  213. on E : Exception do
  214. ShowException(E);
  215. end;
  216. finally
  217. aJSON.Free;
  218. end;
  219. end;
  220. procedure TWebsocketClientApplication.DoControl(Sender: TObject; aType: TFrameType; const aData: TBytes);
  221. var
  222. aReason : String;
  223. aCode : Integer;
  224. begin
  225. inc(fMsgCount);
  226. Case aType of
  227. ftClose:
  228. begin
  229. aCode:=TWSConnection(Sender).GetCloseData(aData,aReason);
  230. Writeln('Close code ',aCode,' received with readon: ',aReason);
  231. end;
  232. ftPing:
  233. begin
  234. Writeln('Ping received');
  235. end;
  236. ftPong:
  237. begin
  238. Writeln('Pong received');
  239. end;
  240. else
  241. Writeln('Unknown control code: ',aType);
  242. end;
  243. end;
  244. procedure TWebsocketClientApplication.DoDisconnect(Sender: TObject);
  245. begin
  246. Writeln('Connection closed, terminating');
  247. Terminate;
  248. end;
  249. procedure TWebsocketClientApplication.DoIncomingMessage(Sender: TObject; const aMessage: TWSMessage);
  250. Var
  251. S,From,Recip : String;
  252. D : TJSONData;
  253. Msg : TJSONObject absolute D;
  254. begin
  255. inc(fMsgCount);
  256. if not aMessage.IsText then
  257. begin
  258. Writeln('Incoming message is not text');
  259. exit;
  260. end;
  261. S:=aMessage.AsString;
  262. try
  263. D:=GetJSON(S);
  264. try
  265. if Not (D is TJSONOBject) then
  266. Raise EJSON.Create('Not an object: '+S);
  267. From:=Msg.Get('from','');
  268. Recip:=Msg.Get('to','');
  269. Write('From <',From,'>');
  270. if SameText(Recip,FAlias) then
  271. Writeln(' to you:')
  272. else
  273. Writeln(' to all:');
  274. Writeln(Msg.Get('msg',''));
  275. finally
  276. FreeAndNil(D)
  277. end;
  278. except
  279. Writeln('Incoming message is not valid JSON: ',S);
  280. end;
  281. end;
  282. Function TWebsocketClientApplication.CheckMessages: boolean;
  283. begin
  284. FMsgCount:=0;
  285. if FUsePump then
  286. CheckSynchronize()
  287. else
  288. while FClient.CheckIncoming=irOK do
  289. ;
  290. Result:=(FMsgCount>0);
  291. end;
  292. var
  293. Application: TWebsocketClientApplication;
  294. begin
  295. Application:=TWebsocketClientApplication.Create(nil);
  296. Application.Title:='Websocket Client Application';
  297. Application.Run;
  298. Application.Free;
  299. end.