IdCmdTCPClient.pas 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311
  1. {
  2. $Project$
  3. $Workfile$
  4. $Revision$
  5. $DateUTC$
  6. $Id$
  7. This file is part of the Indy (Internet Direct) project, and is offered
  8. under the dual-licensing agreement described on the Indy website.
  9. (http://www.indyproject.org/)
  10. Copyright:
  11. (c) 1993-2005, Chad Z. Hower and the Indy Pit Crew. All rights reserved.
  12. }
  13. {
  14. $Log$
  15. }
  16. {
  17. Rev 1.18 2/8/05 5:24:48 PM RLebeau
  18. Updated Disconnect() to not wait for the listening thread to terminate until
  19. after the inherited Disconnect() is called, so that the socket is actually
  20. disconnected and the thread can terminate properly.
  21. Rev 1.17 2/1/05 12:38:30 AM RLebeau
  22. Removed unused CommandHandlersEnabled property
  23. Rev 1.16 6/11/2004 8:48:16 AM DSiders
  24. Added "Do not Localize" comments.
  25. Rev 1.15 5/18/04 9:12:26 AM RLebeau
  26. Bug fix for SetExceptionReply() property setter
  27. Rev 1.14 5/16/04 5:18:04 PM RLebeau
  28. Added setter method to ExceptionReply property
  29. Rev 1.13 5/10/2004 6:10:38 PM DSiders
  30. Removed unused member var FCommandHandlersInitialized.
  31. Rev 1.12 2004.03.06 1:33:00 PM czhower
  32. -Change to disconnect
  33. -Addition of DisconnectNotifyPeer
  34. -WriteHeader now write bufers
  35. Rev 1.11 2004.03.01 5:12:24 PM czhower
  36. -Bug fix for shutdown of servers when connections still existed (AV)
  37. -Implicit HELP support in CMDserver
  38. -Several command handler bugs
  39. -Additional command handler functionality.
  40. Rev 1.10 2004.02.03 4:17:10 PM czhower
  41. For unit name changes.
  42. Rev 1.9 2004.01.20 10:03:22 PM czhower
  43. InitComponent
  44. Rev 1.8 1/4/04 8:46:16 PM RLebeau
  45. Added OnBeforeCommandHandler and OnAfterCommandHandler events
  46. Rev 1.7 11/4/2003 10:25:40 PM DSiders
  47. Removed duplicate FReplyClass member in TIdCmdTCPClient (See
  48. TIdTCPConnection).
  49. Rev 1.6 10/21/2003 10:54:20 AM JPMugaas
  50. Fix for new API change.
  51. Rev 1.5 2003.10.18 9:33:24 PM czhower
  52. Boatload of bug fixes to command handlers.
  53. Rev 1.4 2003.10.02 10:16:26 AM czhower
  54. .Net
  55. Rev 1.3 2003.09.19 11:54:26 AM czhower
  56. -Completed more features necessary for servers
  57. -Fixed some bugs
  58. Rev 1.2 7/9/2003 10:55:24 PM BGooijen
  59. Restored all features
  60. Rev 1.1 7/9/2003 04:36:06 PM JPMugaas
  61. You now can override the TIdReply with your own type. This should illiminate
  62. some warnings about some serious issues. TIdReply is ONLY a base class with
  63. virtual methods.
  64. Rev 1.0 7/7/2003 7:06:40 PM SPerry
  65. Component that uses command handlers
  66. Rev 1.0 7/6/2003 4:47:26 PM SPerry
  67. Units that use Command handlers
  68. }
  69. unit IdCmdTCPClient;
  70. {
  71. Original author: Sergio Perry
  72. Description: TCP client that uses CommandHandlers
  73. }
  74. interface
  75. {$I IdCompilerDefines.inc}
  76. uses
  77. Classes,
  78. IdContext,
  79. IdException,
  80. IdGlobal,
  81. IdReply,
  82. IdResourceStringsCore,
  83. IdThread,
  84. IdTCPClient,
  85. IdCommandHandlers;
  86. type
  87. TIdCmdTCPClient = class;
  88. { Events }
  89. TIdCmdTCPClientAfterCommandHandlerEvent = procedure(ASender: TIdCmdTCPClient;
  90. AContext: TIdContext) of object;
  91. TIdCmdTCPClientBeforeCommandHandlerEvent = procedure(ASender: TIdCmdTCPClient;
  92. var AData: string; AContext: TIdContext) of object;
  93. { Listening Thread }
  94. TIdCmdClientContext = class(TIdContext)
  95. protected
  96. FClient: TIdCmdTCPClient;
  97. public
  98. property Client: TIdCmdTCPClient read FClient;
  99. end;
  100. TIdCmdTCPClientListeningThread = class(TIdThread)
  101. protected
  102. FContext: TIdCmdClientContext;
  103. FClient: TIdCmdTCPClient;
  104. FRecvData: String;
  105. //
  106. procedure Run; override;
  107. public
  108. constructor Create(AClient: TIdCmdTCPClient); reintroduce;
  109. destructor Destroy; override;
  110. //
  111. property Client: TIdCmdTCPClient read FClient;
  112. property RecvData: String read FRecvData write FRecvData;
  113. end;
  114. { TIdCmdTCPClient }
  115. TIdCmdTCPClient = class(TIdTCPClient)
  116. protected
  117. FExceptionReply: TIdReply;
  118. FListeningThread: TIdCmdTCPClientListeningThread;
  119. FCommandHandlers: TIdCommandHandlers;
  120. FOnAfterCommandHandler: TIdCmdTCPClientAfterCommandHandlerEvent;
  121. FOnBeforeCommandHandler: TIdCmdTCPClientBeforeCommandHandlerEvent;
  122. //
  123. procedure DoAfterCommandHandler(ASender: TIdCommandHandlers; AContext: TIdContext);
  124. procedure DoBeforeCommandHandler(ASender: TIdCommandHandlers; var AData: string;
  125. AContext: TIdContext);
  126. procedure DoReplyUnknownCommand(AContext: TIdContext; ALine: string); virtual;
  127. function GetCmdHandlerClass: TIdCommandHandlerClass; virtual;
  128. procedure SetCommandHandlers(AValue: TIdCommandHandlers);
  129. procedure SetExceptionReply(AValue: TIdReply);
  130. public
  131. constructor Create(AOwner: TComponent); override;
  132. destructor Destroy; override;
  133. procedure Connect; override;
  134. procedure Disconnect(ANotifyPeer: Boolean); override;
  135. published
  136. property CommandHandlers: TIdCommandHandlers read FCommandHandlers write SetCommandHandlers;
  137. property ExceptionReply: TIdReply read FExceptionReply write SetExceptionReply;
  138. //
  139. property OnAfterCommandHandler: TIdCmdTCPClientAfterCommandHandlerEvent
  140. read FOnAfterCommandHandler write FOnAfterCommandHandler;
  141. property OnBeforeCommandHandler: TIdCmdTCPClientBeforeCommandHandlerEvent
  142. read FOnBeforeCommandHandler write FOnBeforeCommandHandler;
  143. end;
  144. EIdCmdTCPClientError = class(EIdException);
  145. EIdCmdTCPClientConnectError = class(EIdCmdTCPClientError);
  146. implementation
  147. uses
  148. SysUtils;
  149. type
  150. TIdCmdClientContextAccess = class(TIdCmdClientContext)
  151. end;
  152. { Listening Thread }
  153. constructor TIdCmdTCPClientListeningThread.Create(AClient: TIdCmdTCPClient);
  154. begin
  155. // TODO: move this into TIdCmdTCPClient itself so the Context is always
  156. // available even if the thread is not running...
  157. //
  158. FClient := AClient;
  159. FContext := TIdCmdClientContext.Create(AClient, nil, nil);
  160. FContext.FClient := AClient;
  161. {$I IdObjectChecksOff.inc}
  162. TIdCmdClientContextAccess(FContext).FOwnsConnection := False;
  163. {$I IdObjectChecksOn.inc}
  164. //
  165. inherited Create(False);
  166. end;
  167. destructor TIdCmdTCPClientListeningThread.Destroy;
  168. begin
  169. inherited Destroy;
  170. FContext.Free;
  171. end;
  172. procedure TIdCmdTCPClientListeningThread.Run;
  173. begin
  174. FRecvData := FClient.IOHandler.ReadLn;
  175. if not FClient.CommandHandlers.HandleCommand(FContext, FRecvData) then begin
  176. FClient.DoReplyUnknownCommand(FContext, FRecvData);
  177. end;
  178. //Synchronize(?);
  179. if not Terminated then begin
  180. FClient.IOHandler.CheckForDisconnect;
  181. end;
  182. end;
  183. { TIdCmdTCPClient }
  184. constructor TIdCmdTCPClient.Create(AOwner: TComponent);
  185. var
  186. LHandlerClass: TIdCommandHandlerClass;
  187. begin
  188. inherited Create(AOwner);
  189. FExceptionReply := FReplyClass.Create(nil);
  190. ExceptionReply.SetReply(500, 'Unknown Internal Error'); {do not localize}
  191. LHandlerClass := GetCmdHandlerClass;
  192. FCommandHandlers := TIdCommandHandlers.Create(Self, FReplyClass, nil, ExceptionReply, LHandlerClass);
  193. FCommandHandlers.OnAfterCommandHandler := DoAfterCommandHandler;
  194. FCommandHandlers.OnBeforeCommandHandler := DoBeforeCommandHandler;
  195. end;
  196. destructor TIdCmdTCPClient.Destroy;
  197. begin
  198. Disconnect;
  199. FExceptionReply.Free;
  200. FCommandHandlers.Free;
  201. inherited Destroy;
  202. end;
  203. procedure TIdCmdTCPClient.Connect;
  204. begin
  205. inherited Connect;
  206. //
  207. try
  208. FListeningThread := TIdCmdTCPClientListeningThread.Create(Self);
  209. except
  210. Disconnect(True);
  211. IndyRaiseOuterException(EIdCmdTCPClientConnectError.Create(RSNoCreateListeningThread));
  212. end;
  213. end;
  214. procedure TIdCmdTCPClient.Disconnect(ANotifyPeer: Boolean);
  215. begin
  216. if Assigned(FListeningThread) then begin
  217. FListeningThread.Terminate;
  218. end;
  219. try
  220. inherited Disconnect(ANotifyPeer);
  221. finally
  222. if Assigned(FListeningThread) and not IsCurrentThread(FListeningThread) then begin
  223. FListeningThread.WaitFor;
  224. FreeAndNil(FListeningThread);
  225. end;
  226. end;
  227. end;
  228. procedure TIdCmdTCPClient.DoAfterCommandHandler(ASender: TIdCommandHandlers;
  229. AContext: TIdContext);
  230. begin
  231. if Assigned(OnAfterCommandHandler) then begin
  232. OnAfterCommandHandler(Self, AContext);
  233. end;
  234. end;
  235. procedure TIdCmdTCPClient.DoBeforeCommandHandler(ASender: TIdCommandHandlers;
  236. var AData: string; AContext: TIdContext);
  237. begin
  238. if Assigned(OnBeforeCommandHandler) then begin
  239. OnBeforeCommandHandler(Self, AData, AContext);
  240. end;
  241. end;
  242. procedure TIdCmdTCPClient.DoReplyUnknownCommand(AContext: TIdContext; ALine: string);
  243. begin
  244. end;
  245. function TIdCmdTCPClient.GetCmdHandlerClass: TIdCommandHandlerClass;
  246. begin
  247. Result := TIdCommandHandler;
  248. end;
  249. procedure TIdCmdTCPClient.SetCommandHandlers(AValue: TIdCommandHandlers);
  250. begin
  251. FCommandHandlers.Assign(AValue);
  252. end;
  253. procedure TIdCmdTCPClient.SetExceptionReply(AValue: TIdReply);
  254. begin
  255. FExceptionReply.Assign(AValue);
  256. end;
  257. end.