echoclient.pp 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307
  1. {
  2. Sample program by Andrew Haines to test echo clients.
  3. }
  4. program echoclient;
  5. // Define this if you want to test the woSendErrClosesConn flag.
  6. { $DEFINE USE_NEW_CLOSE_FLAG}
  7. {$mode objfpc}{$H+}
  8. uses
  9. {$IFDEF UNIX}
  10. cthreads,
  11. {$ENDIF}
  12. Classes,
  13. URIParser,
  14. fpwebsocket,
  15. fpwebsocketclient,
  16. opensslsockets,
  17. sysutils;
  18. const
  19. addresses: array[0..4] of String = (
  20. 'ws://127.0.0.1:8080/',
  21. 'ws://ws.vi-server.org:80/mirror',
  22. 'wss://ws.vi-server.org:443/mirror',
  23. 'ws://vi-server.org:1939/',
  24. '' // <-- used for paramStr(1) if present
  25. );
  26. type
  27. { TRunner }
  28. TRunner = class(TComponent)
  29. private type
  30. TStdInThread = class(TThread)
  31. protected
  32. FRunner: TRunner;
  33. StrToSend: String;
  34. procedure Execute; override;
  35. public
  36. constructor Create(ARunner: TRunner);
  37. end;
  38. private
  39. FQuit: Boolean;
  40. FPump: TWSThreadMessagePump;
  41. FClient: TWebsocketClient;
  42. FUri: String;
  43. FStdInRead: TStdInThread;
  44. procedure DoConnect(Sender: TObject);
  45. procedure DoControl(Sender: TObject; aType: TFrameType; const aData: TBytes );
  46. procedure DoDisconnect(Sender: TObject);
  47. procedure DoHandShakeResponse(Sender: TObject;
  48. aResponse: TWSHandShakeResponse; var aAllow: Boolean);
  49. procedure DoMessage(Sender: TObject; const aMessage: TWSMessage);
  50. procedure DoPumpError(Sender: TObject; E: Exception);
  51. procedure DoSendHandshake(Sender: TObject; aHeaders: TStrings);
  52. procedure SyncReadLine;
  53. public
  54. constructor Create(AOwner: TComponent); override;
  55. destructor Destroy; override;
  56. end;
  57. function GetAddress: String;
  58. var
  59. i: Integer;
  60. lRead: String;
  61. lHappy: Boolean = False;
  62. lNum: Longint;
  63. lDefault: Integer = 0;
  64. begin
  65. if ParamCount > 0 then
  66. begin
  67. lDefault := High(addresses);
  68. addresses[lDefault] := ParamStr(1);
  69. end;
  70. while not lHappy do
  71. begin
  72. WriteLn('Type an address or select a server number. "Q" quits');
  73. for i := 0 to High(addresses) do
  74. if addresses[i] <> '' then
  75. WriteLn(Format(' %d) %s',[i, addresses[i]]));
  76. Write(Format('Leave empty for default [%d] :', [lDefault]));
  77. lRead := '';
  78. ReadLn(lRead);
  79. if lRead = 'Q' then
  80. begin
  81. Result := '';
  82. lHappy := True;
  83. end
  84. else if lRead = '' then
  85. begin
  86. lHappy:=True;
  87. Result := addresses[lDefault];
  88. end
  89. else if TryStrToInt(lRead, lNum)
  90. and ((lNum in [0..High(addresses)-1]) or
  91. (lNum = High(addresses)) and (addresses[High(addresses)] <> '')) then
  92. begin
  93. Result := addresses[lNum];
  94. lHappy := True;
  95. end
  96. else if pos('ws', lRead) = 1 then
  97. begin
  98. Result := lRead;
  99. lHappy := True;
  100. end
  101. else
  102. begin
  103. WriteLn('Invalid address. Must be ws[s]://<site>[:port]/');
  104. lHappy := False;
  105. end;
  106. end;
  107. end;
  108. { TRunner.TReadThread }
  109. procedure TRunner.TStdInThread.Execute;
  110. var
  111. lRead: String;
  112. begin
  113. while not Terminated do
  114. begin
  115. Write('Enter text to send. "Q" quits. : ');
  116. ReadLn(lRead);
  117. StrToSend:=Copy(lRead, 1, MaxInt);
  118. Synchronize(@FRunner.SyncReadLine);
  119. Sleep(500); // only for prettier text lines if we get a reply
  120. end;
  121. end;
  122. constructor TRunner.TStdInThread.Create(ARunner: TRunner);
  123. begin
  124. FRunner := ARunner;
  125. inherited Create(False);
  126. end;
  127. { TRunner }
  128. procedure TRunner.DoDisconnect(Sender: TObject);
  129. begin
  130. WriteLn(' -- Disconnect --');
  131. end;
  132. procedure TRunner.DoHandShakeResponse(Sender: TObject;
  133. aResponse: TWSHandShakeResponse; var aAllow: Boolean);
  134. begin
  135. aAllow:=aAllow; // <-- silence compiler error
  136. WriteLn(' -- Handshake Response -- ');
  137. WriteLn(aResponse.RawHeaders.Text);
  138. end;
  139. procedure TRunner.DoMessage(Sender: TObject; const aMessage: TWSMessage);
  140. begin
  141. if aMessage.IsText then
  142. begin
  143. WriteLn('<<< ', aMessage.AsString);
  144. end;
  145. end;
  146. procedure TRunner.DoPumpError(Sender: TObject; E: Exception);
  147. begin
  148. WriteLn(' -- Pump Error: ', E.Message);
  149. Fclient.Active := False;
  150. FQuit:=True;
  151. end;
  152. procedure TRunner.DoSendHandshake(Sender: TObject; aHeaders: TStrings);
  153. begin
  154. {
  155. aHeaders.Values['Accept-Encoding'] := '';
  156. aHeaders.Values['Origin'] := '';
  157. aHeaders.Values['Cache-Control'] := 'no-cache';
  158. aHeaders.Values['Pragma'] := 'no-cache';
  159. aHeaders.Values['Sec-WebSocket-Extensions'] := 'client_max_window_bits';
  160. aHeaders.Values['User-Agent'] := 'My Special Websocket';
  161. }
  162. WriteLn(' -- Sending Handshake ... ---');
  163. WriteLn(aHeaders.Text);
  164. end;
  165. procedure TRunner.DoConnect(Sender: TObject);
  166. begin
  167. WriteLn(Format(' -- Connected to %s --', [FUri]));
  168. end;
  169. procedure TRunner.DoControl(Sender: TObject; aType: TFrameType;
  170. const aData: TBytes);
  171. begin
  172. WriteLn;
  173. WriteLn(' -- Control message: ', aType);
  174. case aType of
  175. ftPing:
  176. begin
  177. if not (woPongExplicit in FClient.Options) then
  178. WriteLn(' -- Pong was sent implicitly ---')
  179. else
  180. begin
  181. WriteLn(' -- Sending Pong Explicitly --- ');
  182. FClient.Pong(Copy(PChar(@aData[0]), 0, Length(aData)));
  183. end;
  184. end;
  185. ftClose:
  186. begin
  187. WriteLn(' -- Close recieved --- ');
  188. WriteLn(' -- Reason: ', TEncoding.UTF8.GetAnsiString(aData));
  189. FClient.Active := False;
  190. end
  191. else
  192. // the compiler is quiet
  193. end;
  194. WriteLn;
  195. end;
  196. procedure TRunner.SyncReadLine;
  197. begin
  198. if (FStdInRead.StrToSend = 'Q') or (not FClient.Active) then
  199. begin
  200. if not FClient.Active then
  201. WriteLn(' --- Client is not active. Quitting ---')
  202. else
  203. WriteLn(' --- Quitting ---');
  204. FStdInRead.Terminate;
  205. FClient.Active:=False;
  206. FQuit := True;
  207. Exit;
  208. end;
  209. WriteLn('>>> ', FStdInRead.StrToSend);
  210. FClient.SendMessage(FStdInRead.StrToSend);
  211. end;
  212. constructor TRunner.Create(AOwner: TComponent);
  213. var
  214. lURI: TURI;
  215. begin
  216. inherited Create(AOwner);
  217. FPump := TWSThreadMessagePump.Create(Self);
  218. FPump.OnError:=@DoPumpError;
  219. FPump.Execute;
  220. FClient := TWebsocketClient.Create(Self);
  221. // Clients must have a mask defined
  222. FClient.OutGoingFrameMask:=Random(MaxInt);
  223. FClient.OnConnect:=@DoConnect;
  224. FCLient.OnControl:=@DoControl;
  225. FClient.OnDisconnect:=@DoDisconnect;
  226. FClient.OnMessageReceived:=@DoMessage;
  227. FClient.OnSendHandShake:=@DoSendHandshake;
  228. Fclient.OnHandshakeResponse:=@DoHandShakeResponse;
  229. FClient.MessagePump := FPump;
  230. {$IFDEF USE_NEW_CLOSE_FLAG}
  231. FClient.Options:=[woSendErrClosesConn];
  232. {$ENDIF}
  233. FUri := GetAddress;
  234. if FUri = '' then
  235. begin
  236. FQuit:=True;
  237. Exit;
  238. end;
  239. lUri := ParseURI(FUri);
  240. if (lURI.Port = 0) then
  241. begin
  242. if lURI.Protocol = 'ws' then
  243. lURI.Port:=80
  244. else
  245. lURI.Port:=443;
  246. end;
  247. FClient.HostName:=lURI.Host;
  248. FClient.Port:=lURI.Port;
  249. FClient.Resource:=lURI.Path;
  250. FClient.UseSSL:=lURI.Protocol='wss';
  251. FClient.Active:=True;
  252. FStdInRead := TStdInThread.Create(Self);
  253. end;
  254. destructor TRunner.Destroy;
  255. begin
  256. if Assigned(FStdInRead) then
  257. begin
  258. FStdInRead.Terminate;
  259. FStdInRead.WaitFor;
  260. FreeAndNil(FStdInRead);
  261. end;
  262. inherited Destroy;
  263. end;
  264. var
  265. Runner: TRunner;
  266. begin
  267. Runner := TRunner.Create(nil);
  268. try
  269. while not Runner.FQuit do
  270. CheckSynchronize(5000);
  271. finally
  272. Runner.Free;
  273. end;
  274. end.