IdTCPServer.pas 38 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141
  1. { $HDR$}
  2. {**********************************************************************}
  3. { Unit archived using Team Coherence }
  4. { Team Coherence is Copyright 2002 by Quality Software Components }
  5. { }
  6. { For further information / comments, visit our WEB site at }
  7. { http://www.TeamCoherence.com }
  8. {**********************************************************************}
  9. {}
  10. { $Log: 10367: IdTCPServer.pas
  11. {
  12. { Rev 1.5 2004.02.26 7:00:52 PM czhower
  13. { BBG: TIdPeerThread loses IOHandler
  14. }
  15. {
  16. { Rev 1.4 1/29/04 9:29:40 PM RLebeau
  17. { Added setter methods to various TIdRFCReply properties
  18. }
  19. {
  20. { Rev 1.3 1/15/04 7:43:54 PM RLebeau
  21. { Updated TerminateAllThreads() so that the Threads list count could be tested
  22. { one more time after the final Sleep(), just in case of a last-moment thread
  23. { termination during the Sleep that satisfies the timeout.
  24. }
  25. {
  26. Rev 1.2 3/22/2003 1:56:40 AM BGooijen
  27. Fixed a bug where non-paged memory was leaked when an exception occured in
  28. TIdListenerThread.Run
  29. }
  30. {
  31. Rev 1.1 3/21/2003 4:51:50 PM BGooijen
  32. Intercept is freed in TIdPeerThread.AfterRun if a ServerIntercept is assigned
  33. to the server
  34. }
  35. {
  36. { Rev 1.0 2002.11.12 10:55:14 PM czhower
  37. }
  38. unit IdTCPServer;
  39. interface
  40. {
  41. Original Author and Maintainer:
  42. - Chad Z. Hower a.k.a Kudzu
  43. 2002-01-01 - Andrew P.Rybin
  44. - bug fix (MaxConnections, SetActive(FALSE)), TerminateListenerThreads, DoExecute
  45. 2002-04-17 - Andrew P.Rybin
  46. - bug fix: if exception raised in OnConnect, Threads.Remove and ThreadMgr.ReleaseThread are not called
  47. }
  48. uses
  49. Classes, SysUtils,
  50. IdComponent, IdException, IdSocketHandle, IdTCPConnection, IdThread, IdThreadMgr,
  51. IdIOHandlerSocket, IdIOHandler, IdThreadMgrDefault, IdIntercept, IdStackConsts,
  52. IdGlobal, IdRFCReply, IdServerIOHandler, IdServerIOHandlerSocket;
  53. const
  54. IdEnabledDefault = True;
  55. // DO NOT change this defualt (ParseParams). Many servers rely on this
  56. IdParseParamsDefault = True;
  57. IdCommandHandlersEnabledDefault = True;
  58. IdListenQueueDefault = 15;
  59. type
  60. TIdCommandHandler = class;
  61. TIdCommand = class;
  62. TIdPeerThread = class;
  63. TIdTCPServer = class;
  64. TIdAfterCommandHandlerEvent = procedure(ASender: TIdTCPServer; AThread: TIdPeerThread) of object;
  65. TIdBeforeCommandHandlerEvent = procedure(ASender: TIdTCPServer; const AData: string;
  66. AThread: TIdPeerThread) of object;
  67. TIdCommandEvent = procedure(ASender: TIdCommand) of object;
  68. TIdNoCommandHandlerEvent = procedure(ASender: TIdTCPServer; const AData: string;
  69. AThread: TIdPeerThread) of object;
  70. TIdCommandHandler = class(TCollectionItem)
  71. protected
  72. FCmdDelimiter: Char;
  73. FCommand: string;
  74. FData: TObject;
  75. FDisconnect: boolean;
  76. FEnabled: boolean;
  77. FName: string;
  78. FOnCommand: TIdCommandEvent;
  79. FParamDelimiter: Char;
  80. FParseParams: Boolean;
  81. FReplyExceptionCode: Integer;
  82. FReplyNormal: TIdRFCReply;
  83. FResponse: TStrings;
  84. FTag: integer;
  85. //
  86. function GetDisplayName: string; override;
  87. procedure SetDisplayName(const AValue: string); override;
  88. procedure SetReplyNormal(AValue: TIdRFCReply);
  89. procedure SetResponse(AValue: TStrings);
  90. public
  91. function Check(const AData: string; AThread: TIdPeerThread): boolean; virtual;
  92. constructor Create(ACollection: TCollection); override;
  93. destructor Destroy; override;
  94. function GetNamePath: string; override;
  95. function NameIs(ACommand: string): Boolean;
  96. //
  97. property Data: TObject read FData write FData;
  98. published
  99. property CmdDelimiter: Char read FCmdDelimiter write FCmdDelimiter;
  100. property Command: string read FCommand write FCommand;
  101. property Disconnect: boolean read FDisconnect write FDisconnect;
  102. property Enabled: boolean read FEnabled write FEnabled default IdEnabledDefault;
  103. property Name: string read FName write FName;
  104. property OnCommand: TIdCommandEvent read FOnCommand write FOnCommand;
  105. property ParamDelimiter: Char read FParamDelimiter write FParamDelimiter;
  106. property ParseParams: Boolean read FParseParams write FParseParams default IdParseParamsDefault;
  107. property ReplyExceptionCode: Integer read FReplyExceptionCode write FReplyExceptionCode;
  108. property ReplyNormal: TIdRFCReply read FReplyNormal write SetReplyNormal;
  109. property Response: TStrings read FResponse write SetResponse;
  110. property Tag: integer read FTag write FTag;
  111. end;
  112. TIdCommandHandlers = class(TOwnedCollection)
  113. protected
  114. FServer: TIdTCPServer;
  115. //
  116. function GetItem(AIndex: Integer): TIdCommandHandler;
  117. // This is used instead of the OwnedBy property directly calling GetOwner because
  118. // D5 dies with internal errors and crashes
  119. function GetOwnedBy: TPersistent;
  120. procedure SetItem(AIndex: Integer; const AValue: TIdCommandHandler);
  121. public
  122. function Add: TIdCommandHandler;
  123. constructor Create(AServer: TIdTCPServer); reintroduce;
  124. //
  125. property Items[AIndex: Integer]: TIdCommandHandler read GetItem write SetItem;
  126. // OwnedBy is used so as not to conflict with Owner in D6
  127. property OwnedBy: TPersistent read GetOwnedBy;
  128. property Server: TIdTCPServer read FServer;
  129. end;
  130. TIdCommand = class(TObject)
  131. protected
  132. FCommandHandler: TIdCommandHandler;
  133. FParams: TStrings;
  134. FPerformReply: Boolean;
  135. FRawLine: string;
  136. FReply: TIdRFCReply;
  137. FResponse: TStrings;
  138. FThread: TIdPeerThread;
  139. FUnparsedParams: string;
  140. //
  141. procedure DoCommand; virtual;
  142. procedure SetReply(AValue: TIdRFCReply);
  143. public
  144. constructor Create; virtual;
  145. destructor Destroy; override;
  146. procedure SendReply;
  147. procedure SetResponse(AValue: TStrings);
  148. //
  149. property CommandHandler: TIdCommandHandler read FCommandHandler;
  150. property PerformReply: Boolean read FPerformReply write FPerformReply;
  151. property Params: TStrings read FParams;
  152. property RawLine: string read FRawLine;
  153. property Reply: TIdRFCReply read FReply write SetReply;
  154. property Response: TStrings read FResponse write SetResponse;
  155. property Thread: TIdPeerThread read FThread;
  156. property UnparsedParams: string read FUnparsedParams;
  157. end;
  158. // This is the thread that listens for incoming connections and spawns
  159. // new ones to handle each one
  160. TIdListenerThread = class(TIdThread)
  161. protected
  162. FBinding: TIdSocketHandle;
  163. FServer: TIdTCPServer;
  164. procedure AfterRun; override;
  165. procedure Run; override;
  166. public
  167. constructor Create(AServer: TIdTCPServer; ABinding: TIdSocketHandle); reintroduce;
  168. //
  169. property Binding: TIdSocketHandle read FBinding write FBinding;
  170. property Server: TIdTCPServer read FServer;
  171. End;//TIdListenerThread
  172. TIdTCPServerConnection = class(TIdTCPConnection)
  173. protected
  174. FServer: TIdTCPServer;
  175. // FLastRcvTimeStamp: TDateTime; //Timestamp of latest received command
  176. // FProcessingTimeout: boolean; //To avoid double timeout processing
  177. //
  178. public
  179. // property LastRcvTimeStamp: TDateTime read fLastRcvTimeStamp write fLastRcvTimeStamp;
  180. // property ProcessingTimeout: boolean read fbProcessingTimeout write fbProcessingTimeout;
  181. // function Read(const piLen: Integer): string; override;
  182. constructor Create(AServer: TIdTCPServer); reintroduce;
  183. published
  184. property Server: TIdTCPServer read FServer;
  185. end;
  186. TIdPeerThread = class(TIdThread)
  187. protected
  188. FConnection: TIdTCPServerConnection;
  189. //
  190. procedure AfterRun; override;
  191. procedure BeforeRun; override;
  192. procedure Cleanup; override;
  193. // If things need freed, free them in AfterRun so that pooled threads clean themselves up.
  194. // Only persistent things should be handled in AfterExecute (Destroy)
  195. procedure Run; override;
  196. public
  197. //
  198. property Connection: TIdTCPServerConnection read FConnection;
  199. End;//TIdPeerThread
  200. TIdListenExceptionEvent = procedure(AThread: TIdListenerThread; AException: Exception) of object;
  201. TIdServerThreadExceptionEvent = procedure(AThread: TIdPeerThread; AException: Exception)
  202. of object;
  203. TIdServerThreadEvent = procedure(AThread: TIdPeerThread) of object;
  204. TIdTCPServer = class(TIdComponent)
  205. protected
  206. FActive: Boolean;
  207. FThreadMgr: TIdThreadMgr;
  208. FBindings: TIdSocketHandles;
  209. FCommandHandlers: TIdCommandHandlers;
  210. FCommandHandlersEnabled: Boolean;
  211. FCommandHandlersInitialized: Boolean;
  212. FGreeting: TIdRFCReply;
  213. FImplicitThreadMgr: Boolean;
  214. FImplicitIOHandler: Boolean;
  215. FIntercept: TIdServerIntercept;
  216. FIOHandler: TIdServerIOHandler;
  217. FListenerThreads: TThreadList;
  218. FListenQueue: integer;
  219. FMaxConnectionReply: TIdRFCReply;
  220. FMaxConnections: Integer;
  221. FReplyTexts: TIdRFCReplies;
  222. FReuseSocket: TIdReuseSocket;
  223. FTerminateWaitTime: Integer;
  224. FThreadClass: TIdThreadClass;
  225. FThreads: TThreadList;
  226. FOnAfterCommandHandler: TIdAfterCommandHandlerEvent;
  227. FOnBeforeCommandHandler: TIdBeforeCommandHandlerEvent;
  228. FOnConnect: TIdServerThreadEvent;
  229. FOnDisconnect: TIdServerThreadEvent;
  230. FOnException: TIdServerThreadExceptionEvent;
  231. FOnExecute: TIdServerThreadEvent;
  232. FOnListenException: TIdListenExceptionEvent;
  233. FOnNoCommandHandler: TIdNoCommandHandlerEvent;
  234. FReplyExceptionCode: Integer;
  235. FReplyUnknownCommand: TIdRFCReply;
  236. //
  237. procedure CheckActive;
  238. procedure DoAfterCommandHandler(AThread: TIdPeerThread);
  239. procedure DoBeforeCommandHandler(AThread: TIdPeerThread; const ALine: string);
  240. procedure DoConnect(AThread: TIdPeerThread); virtual;
  241. procedure DoDisconnect(AThread: TIdPeerThread); virtual;
  242. procedure DoException(AThread: TIdPeerThread; AException: Exception);
  243. function DoExecute(AThread: TIdPeerThread): boolean; virtual;
  244. procedure DoListenException(AThread: TIdListenerThread; AException: Exception);
  245. procedure DoOnNoCommandHandler(const AData: string; AThread: TIdPeerThread);
  246. function GetDefaultPort: integer;
  247. function GetThreadMgr: TIdThreadMgr;
  248. procedure InitializeCommandHandlers; virtual;
  249. procedure Notification(AComponent: TComponent; Operation: TOperation); override;
  250. procedure SetActive(AValue: Boolean); virtual;
  251. procedure SetBindings(const AValue: TIdSocketHandles); virtual;
  252. procedure SetDefaultPort(const AValue: integer); virtual;
  253. procedure SetGreeting(AValue: TIdRFCReply);
  254. procedure SetIntercept(const AValue: TIdServerIntercept); virtual;
  255. procedure SetIOHandler(const AValue: TIdServerIOHandler); virtual;
  256. procedure SetMaxConnectionReply(AValue: TIdRFCReply);
  257. procedure SetReplyUnknownCommand(AValue: TIdRFCReply);
  258. procedure SetThreadMgr(const AValue: TIdThreadMgr); virtual;
  259. procedure TerminateAllThreads;
  260. procedure TerminateListenerThreads; //APR
  261. public
  262. constructor Create(AOwner: TComponent); override;
  263. destructor Destroy; override;
  264. procedure Loaded; override;
  265. //
  266. property ImplicitIOHandler: Boolean read FImplicitIOHandler;
  267. property ImplicitThreadMgr: Boolean read FImplicitThreadMgr;
  268. property ThreadClass: TIdThreadClass read FThreadClass write FThreadClass;
  269. property Threads: TThreadList read FThreads;
  270. published
  271. property Active: Boolean read FActive write SetActive default False;
  272. property Bindings: TIdSocketHandles read FBindings write SetBindings;
  273. property CommandHandlers: TIdCommandHandlers read FCommandHandlers write FCommandHandlers;
  274. property CommandHandlersEnabled: boolean read FCommandHandlersEnabled
  275. write FCommandHandlersEnabled default IdCommandHandlersEnabledDefault;
  276. property DefaultPort: integer read GetDefaultPort write SetDefaultPort;
  277. property Greeting: TIdRFCReply read FGreeting write SetGreeting;
  278. property Intercept: TIdServerIntercept read FIntercept write SetIntercept;
  279. property IOHandler: TIdServerIOHandler read FIOHandler write SetIOHandler;
  280. property ListenQueue: integer read FListenQueue write FListenQueue default IdListenQueueDefault;
  281. property MaxConnectionReply: TIdRFCReply read FMaxConnectionReply write SetMaxConnectionReply;
  282. property MaxConnections: Integer read FMaxConnections write FMaxConnections default 0;
  283. // Occurs in the context of the peer thread
  284. property OnAfterCommandHandler: TIdAfterCommandHandlerEvent read FOnAfterCommandHandler
  285. write FOnAfterCommandHandler;
  286. // Occurs in the context of the peer thread
  287. property OnBeforeCommandHandler: TIdBeforeCommandHandlerEvent read FOnBeforeCommandHandler
  288. write FOnBeforeCommandHandler;
  289. // Occurs in the context of the peer thread
  290. property OnConnect: TIdServerThreadEvent read FOnConnect write FOnConnect;
  291. // Occurs in the context of the peer thread
  292. property OnExecute: TIdServerThreadEvent read FOnExecute write FOnExecute;
  293. // Occurs in the context of the peer thread
  294. property OnDisconnect: TIdServerThreadEvent read FOnDisconnect write FOnDisconnect;
  295. // Occurs in the context of the peer thread
  296. property OnException: TIdServerThreadExceptionEvent read FOnException write FOnException;
  297. property OnListenException: TIdListenExceptionEvent read FOnListenException
  298. write FOnListenException;
  299. property OnNoCommandHandler: TIdNoCommandHandlerEvent read FOnNoCommandHandler
  300. write FOnNoCommandHandler;
  301. property ReplyExceptionCode: Integer read FReplyExceptionCode write FReplyExceptionCode;
  302. property ReplyTexts: TIdRFCReplies read FReplyTexts write FReplyTexts;
  303. property ReplyUnknownCommand: TIdRFCReply read FReplyUnknownCommand write SetReplyUnknownCommand;
  304. property ReuseSocket: TIdReuseSocket read FReuseSocket write FReuseSocket default rsOSDependent;
  305. property TerminateWaitTime: Integer read FTerminateWaitTime write FTerminateWaitTime
  306. default 5000;
  307. property ThreadMgr: TIdThreadMgr read GetThreadMgr write SetThreadMgr;
  308. end;
  309. EIdTCPServerError = class(EIdException);
  310. EIdNoExecuteSpecified = class(EIdTCPServerError);
  311. EIdTerminateThreadTimeout = class(EIdTCPServerError);
  312. implementation
  313. uses
  314. IdResourceStrings, IdStack, IdStrings, IdThreadSafe;
  315. { TIdTCPServer }
  316. procedure TIdTCPServer.CheckActive;
  317. begin
  318. if Active and (not (csDesigning in ComponentState)) and (not (csLoading in ComponentState))
  319. then begin
  320. raise EIdTCPServerError.Create(RSCannotPerformTaskWhileServerIsActive);
  321. end;
  322. end;
  323. constructor TIdTCPServer.Create(AOwner: TComponent);
  324. begin
  325. inherited Create(AOwner);
  326. FBindings := TIdSocketHandles.Create(Self);
  327. // Before Command handlers
  328. FReplyTexts := TIdRFCReplies.Create(Self);
  329. FCommandHandlers := TIdCommandHandlers.Create(Self);
  330. FCommandHandlersEnabled := IdCommandHandlersEnabledDefault;
  331. FGreeting := TIdRFCReply.Create(nil);
  332. FMaxConnectionReply := TIdRFCReply.Create(nil);
  333. FThreads := TThreadList.Create;
  334. FThreadClass := TIdPeerThread;
  335. FReplyUnknownCommand := TIdRFCReply.Create(nil);
  336. //
  337. FTerminateWaitTime := 5000;
  338. FListenQueue := IdListenQueueDefault;
  339. //TODO: When reestablished, use a sleeping thread instead
  340. // fSessionTimer := TTimer.Create(self);
  341. end;
  342. destructor TIdTCPServer.Destroy;
  343. begin
  344. Active := False;
  345. if Assigned(FIOHandler) and FImplicitIOHandler then begin
  346. FreeAndNil(FIOHandler);
  347. end;
  348. // Destroy bindings first
  349. FreeAndNil(FBindings);
  350. //
  351. FreeAndNil(FReplyUnknownCommand);
  352. FreeAndNil(FReplyTexts);
  353. FreeAndNil(FThreads);
  354. FreeAndNil(FMaxConnectionReply);
  355. FreeAndNil(FGreeting);
  356. FreeAndNil(FCommandHandlers);
  357. inherited Destroy;
  358. end;
  359. procedure TIdTCPServer.DoAfterCommandHandler(AThread: TIdPeerThread);
  360. begin
  361. if Assigned(OnAfterCommandHandler) then begin
  362. OnAfterCommandHandler(Self, AThread);
  363. end;
  364. end;
  365. procedure TIdTCPServer.DoBeforeCommandHandler(AThread: TIdPeerThread; const ALine: string);
  366. begin
  367. if Assigned(OnBeforeCommandHandler) then begin
  368. OnBeforeCommandHandler(Self, ALine, AThread);
  369. end;
  370. end;
  371. procedure TIdTCPServer.DoConnect(AThread: TIdPeerThread);
  372. begin
  373. ReplyTexts.UpdateText(Greeting);
  374. AThread.Connection.WriteRFCReply(Greeting);
  375. if Assigned(OnConnect) then begin
  376. OnConnect(AThread);
  377. end;
  378. end;
  379. procedure TIdTCPServer.DoDisconnect(AThread: TIdPeerThread);
  380. begin
  381. if Assigned(OnDisconnect) then begin
  382. OnDisconnect(AThread);
  383. end;
  384. end;
  385. procedure TIdTCPServer.DoException(AThread: TIdPeerThread; AException: Exception);
  386. begin
  387. if Assigned(OnException) then begin
  388. OnException(AThread, AException);
  389. end;
  390. end;
  391. function TIdTCPServer.DoExecute(AThread: TIdPeerThread): boolean;
  392. var
  393. I,L: integer;
  394. LLine: string;
  395. begin
  396. L := CommandHandlers.Count-1;
  397. if CommandHandlersEnabled and (L >= 0) then begin
  398. Result := TRUE;
  399. if AThread.Connection.Connected then begin //APR: was While, but user can disable handlers
  400. LLine := AThread.Connection.ReadLn;
  401. // OLX sends blank lines during reset groups and expects no response. Not sure
  402. // what the RFCs say about blank lines.
  403. // I telnetted to some newsservers, and they dont respond to blank lines.
  404. // This unit is core and not NNTP, but we should be consistent.
  405. if LLine <> '' then begin
  406. DoBeforeCommandHandler(AThread, LLine);
  407. try
  408. i := 0;
  409. while i<=L do begin
  410. with CommandHandlers.Items[i] do begin
  411. if Enabled and Check(LLine, AThread) then begin
  412. Break;
  413. end;
  414. end;
  415. inc(i);
  416. end;//while
  417. if i > L then begin
  418. DoOnNoCommandHandler(LLine, AThread);
  419. end;
  420. finally DoAfterCommandHandler(AThread); end;
  421. end;//if >''
  422. end;
  423. end else begin
  424. Result := Assigned(OnExecute);
  425. if Result then begin
  426. OnExecute(AThread);
  427. end;
  428. end;
  429. end;
  430. procedure TIdTCPServer.DoListenException(AThread: TIdListenerThread; AException: Exception);
  431. begin
  432. if Assigned(FOnListenException) then begin
  433. FOnListenException(AThread, AException);
  434. end;
  435. end;
  436. procedure TIdTCPServer.DoOnNoCommandHandler(const AData: string; AThread: TIdPeerThread);
  437. begin
  438. if Assigned(OnNoCommandHandler) then begin
  439. OnNoCommandHandler(Self, AData, AThread);
  440. end else if ReplyUnknownCommand.ReplyExists then begin
  441. //Do not UpdateText here - in thread. Is done in constructor
  442. // TODO: wrong command name is frequently required
  443. AThread.Connection.WriteRFCReply(ReplyUnknownCommand);
  444. end else begin
  445. raise EIdTCPServerError.Create(RSNoCommandHandlerFound);
  446. end;
  447. end;
  448. function TIdTCPServer.GetDefaultPort: integer;
  449. begin
  450. Result := FBindings.DefaultPort;
  451. end;
  452. procedure TIdTCPServer.Loaded;
  453. begin
  454. inherited Loaded;
  455. // Active = True must not be performed before all other props are loaded
  456. if Active then begin
  457. FActive := False;
  458. Active := True;
  459. end;
  460. end;
  461. procedure TIdTCPServer.Notification(AComponent: TComponent; Operation: TOperation);
  462. begin
  463. inherited Notification(AComponent, Operation);
  464. // remove the reference to the linked components if they are deleted
  465. if (Operation = opRemove) then begin
  466. if (AComponent = FThreadMgr) then begin
  467. TerminateAllThreads;
  468. FThreadMgr := nil;
  469. end else if (AComponent = FIntercept) then begin
  470. FIntercept := nil;
  471. end else if (AComponent = FIOHandler) then begin
  472. FIOHandler := nil;
  473. end;
  474. end;
  475. end;
  476. procedure TIdTCPServer.SetActive(AValue: Boolean);
  477. var
  478. i: Integer;
  479. LListenerThread: TIdListenerThread;
  480. begin
  481. // SG 28/11/01: removed the "try..finally FActive := AValue; end;" wrapper
  482. // SG 28/11/01: It cause the component to be locked in the "active" state, even if
  483. // SG 28/11/01: the socket couldn't be bound.
  484. if (not (csDesigning in ComponentState)) and (FActive <> AValue)
  485. and (not (csLoading in ComponentState)) then begin
  486. if AValue then begin
  487. // InitializeCommandHandlers must be called only at runtime, and only after streaming
  488. // has occured. This used to be in .Loaded and that worked for forms. It failed
  489. // for dynamically created instances and also for descendant classes.
  490. if not FCommandHandlersInitialized then begin
  491. FCommandHandlersInitialized := True;
  492. InitializeCommandHandlers;
  493. end;
  494. // Set up bindings
  495. if Bindings.Count = 0 then begin
  496. Bindings.Add;
  497. end;
  498. // Set up ThreadMgr
  499. ThreadMgr.ThreadClass := ThreadClass;
  500. // Setup IOHandler
  501. if not Assigned(FIOHandler) then begin
  502. IOHandler := TIdServerIOHandlerSocket.Create(self);
  503. FImplicitIOHandler := true;
  504. end;
  505. // Update reply texts for "global" replies
  506. ReplyTexts.UpdateText(ReplyUnknownCommand);
  507. ReplyTexts.UpdateText(MaxConnectionReply);
  508. // Set up listener threads
  509. IOHandler.Init;
  510. i := 0;
  511. try
  512. while i < Bindings.Count do begin
  513. with Bindings[i] do begin
  514. AllocateSocket;
  515. if (FReuseSocket = rsTrue) or ((FReuseSocket = rsOSDependent) and (GOSType = otLinux))
  516. then begin
  517. SetSockOpt(Id_SOL_SOCKET, Id_SO_REUSEADDR, PChar(@Id_SO_True), SizeOf(Id_SO_True));
  518. end;
  519. Bind;
  520. end;
  521. Inc(i);
  522. end;
  523. except
  524. Dec(i); // the one that failed doesn't need to be closed
  525. while i >= 0 do begin
  526. Bindings[i].CloseSocket;
  527. Dec(i);
  528. end;
  529. FActive := True;
  530. SetActive(False); // allow descendants to clean up
  531. raise;
  532. end;
  533. FListenerThreads := TThreadList.Create;
  534. for i := 0 to Bindings.Count - 1 do begin
  535. Bindings[i].Listen(FListenQueue);
  536. LListenerThread := TIdListenerThread.Create(Self, Bindings[i]);
  537. FListenerThreads.Add(LListenerThread);
  538. LListenerThread.Start;
  539. end;
  540. end else begin
  541. TerminateListenerThreads;
  542. // Tear down ThreadMgr
  543. try
  544. TerminateAllThreads;
  545. finally
  546. if ImplicitThreadMgr and TIdThreadSafeList(Threads).IsCountLessThan(1) then begin // DONE -oAPR: BUG! Threads still live, Mgr dead ;-(
  547. FreeAndNil(FThreadMgr);
  548. FImplicitThreadMgr := False;
  549. end;
  550. end;//tryf
  551. end;
  552. end;
  553. FActive := AValue;
  554. end;
  555. procedure TIdTCPServer.SetBindings(const AValue: TIdSocketHandles);
  556. begin
  557. FBindings.Assign(AValue);
  558. end;
  559. procedure TIdTCPServer.SetDefaultPort(const AValue: integer);
  560. begin
  561. FBindings.DefaultPort := AValue;
  562. end;
  563. procedure TIdTCPServer.SetGreeting(AValue: TIdRFCReply);
  564. begin
  565. FGreeting.Assign(AValue);
  566. end;
  567. procedure TIdTCPServer.SetMaxConnectionReply(AValue: TIdRFCReply);
  568. begin
  569. FMaxConnectionReply.Assign(AValue);
  570. end;
  571. procedure TIdTCPServer.SetReplyUnknownCommand(AValue: TIdRFCReply);
  572. begin
  573. FReplyUnknownCommand.Assign(AValue);
  574. end;
  575. procedure TIdTCPServer.SetIntercept(const AValue: TIdServerIntercept);
  576. begin
  577. FIntercept := AValue;
  578. // Add self to the intercept's notification list
  579. if assigned(FIntercept) then
  580. begin
  581. FIntercept.FreeNotification(Self);
  582. end;
  583. end;
  584. procedure TIdTCPServer.SetThreadMgr(const AValue: TIdThreadMgr);
  585. begin
  586. if ImplicitThreadMgr then
  587. begin
  588. // Free the default Thread manager
  589. FreeAndNil(FThreadMgr);
  590. FImplicitThreadMgr := false;
  591. end;
  592. FThreadMgr := AValue;
  593. // Ensure we will be notified when the component is freed, even is it's on
  594. // another form
  595. if AValue <> nil then begin
  596. AValue.FreeNotification(self);
  597. end;
  598. end;
  599. procedure TIdTCPServer.SetIOHandler(const AValue: TIdServerIOHandler);
  600. begin
  601. if Assigned(FIOHandler) and FImplicitIOHandler then begin
  602. FImplicitIOHandler := false;
  603. FreeAndNil(FIOHandler);
  604. end;
  605. FIOHandler := AValue;
  606. if AValue <> nil then begin
  607. AValue.FreeNotification(self);
  608. end;
  609. end;
  610. //APR-011207: for safe-close Ex: SQL Server ShutDown 1) stop listen 2) wait until all clients go out
  611. procedure TIdTCPServer.TerminateListenerThreads;
  612. var
  613. i: Integer;
  614. LListenerThread: TIdListenerThread;
  615. LListenerThreads: TList;
  616. Begin
  617. if Assigned(FListenerThreads) then begin
  618. LListenerThreads := FListenerThreads.LockList;
  619. try
  620. for i:= 0 to LListenerThreads.Count - 1 do begin
  621. LListenerThread := TIdListenerThread(LListenerThreads[i]);
  622. with LListenerThread do begin
  623. // Stop listening
  624. Terminate;
  625. Binding.CloseSocket;
  626. // Tear down Listener thread
  627. WaitFor;
  628. Free;
  629. end;
  630. end;
  631. finally FListenerThreads.UnlockList; end;
  632. FreeAndNil(FListenerThreads);
  633. end;//if
  634. End;//TerminateListenerThreads
  635. procedure TIdTCPServer.TerminateAllThreads;
  636. const
  637. LSleepTime: Integer = 250;
  638. var
  639. i: Integer;
  640. LThreads: TList;
  641. LTimedOut: Boolean;
  642. begin
  643. // Threads will be nil if exception happens during start up, such as trying to bind to a port
  644. // that is already in use.
  645. if Assigned(Threads) then begin
  646. // This will provide us with posibility to call AThread.Notification in OnDisconnect event handler
  647. // in order to access visual components. They can add notifications after the list has been
  648. // unlocked, and before/while TerminateThreads is called
  649. LThreads := Threads.LockList; try
  650. for i := 0 to LThreads.Count - 1 do begin
  651. with TIdPeerThread(LThreads[i]) do begin
  652. Connection.DisconnectSocket;
  653. end;
  654. end;
  655. finally Threads.UnlockList; end;
  656. // Must wait for all threads to terminate, as they access the server and bindings. If this
  657. // routine is being called from the destructor, this can cause AVs
  658. //
  659. // This method is used instead of:
  660. // -Threads.WaitFor. Since they are being destroyed thread. WaitFor could AV. And Waiting for
  661. // Handle produces different code for different OSs, and using common code has troubles
  662. // as the handles are quite different.
  663. // -Last thread signaling
  664. // ThreadMgr.TerminateThreads(TerminateWaitTime);
  665. if not TIdThreadSafeList(Threads).IsCountLessThan(1) then begin
  666. LTimedOut := True;
  667. for i := 1 to (TerminateWaitTime div LSleepTime) do begin
  668. Sleep(LSleepTime);
  669. if TIdThreadSafeList(Threads).IsCountLessThan(1) then begin
  670. LTimedOut := False;
  671. Break;
  672. end;
  673. end;
  674. if LTimedOut then begin
  675. raise EIdTerminateThreadTimeout.Create(RSTerminateThreadTimeout);
  676. end;
  677. end;
  678. end;
  679. End;//TerminateAllThreads
  680. function TIdTCPServer.GetThreadMgr: TIdThreadMgr;
  681. begin
  682. if (not (csDesigning in ComponentState)) and (not Assigned(FThreadMgr)) then
  683. begin
  684. // Set up ThreadMgr
  685. ThreadMgr := TIdThreadMgrDefault.Create(Self);
  686. FImplicitThreadMgr := true;
  687. end;
  688. Result := FThreadMgr;
  689. end;
  690. procedure TIdTCPServer.InitializeCommandHandlers;
  691. begin
  692. end;
  693. { TIdListenerThread }
  694. procedure TIdListenerThread.AfterRun;
  695. begin
  696. inherited AfterRun;
  697. // Close just your own binding. The rest will be closed
  698. // from their coresponding threads
  699. FBinding.CloseSocket;
  700. end;
  701. constructor TIdListenerThread.Create(AServer: TIdTCPServer; ABinding: TIdSocketHandle);
  702. begin
  703. inherited Create;
  704. FBinding := ABinding;
  705. FServer := AServer;
  706. end;
  707. procedure TIdListenerThread.Run;
  708. var
  709. LIOHandler: TIdIOHandler;
  710. LPeer: TIdTCPServerConnection;
  711. LThread: TIdPeerThread;
  712. begin
  713. try
  714. if Assigned(Server) then begin // This is temporary code just to test one exception
  715. while True do begin
  716. LThread := nil;
  717. LPeer := TIdTCPServerConnection.Create(Server);
  718. LIOHandler := Server.IOHandler.Accept(Binding.Handle, SELF);
  719. if LIOHandler = nil then begin
  720. FreeAndNil(LPeer);
  721. Stop;
  722. Exit;
  723. end
  724. else begin
  725. LThread := TIdPeerThread(Server.ThreadMgr.GetThread);
  726. LThread.FConnection := LPeer;
  727. LThread.FConnection.IOHandler := LIOHandler;
  728. LThread.FConnection.FFreeIOHandlerOnDisconnect := true;
  729. end;
  730. // LastRcvTimeStamp := Now; // Added for session timeout support
  731. // ProcessingTimeout := False;
  732. if (Server.MaxConnections > 0) and // Check MaxConnections
  733. NOT TIdThreadSafeList(Server.Threads).IsCountLessThan(Server.MaxConnections)
  734. then begin
  735. //Do not UpdateText here - in thread. Is done in constructor
  736. LPeer.WriteRFCReply(Server.MaxConnectionReply);
  737. LPeer.Disconnect;
  738. Server.ThreadMgr.ReleaseThread(LThread); // Give the thread back to the thread-manager
  739. end else begin
  740. Server.Threads.Add(LThread); //APR
  741. // Start Peer Thread
  742. LThread.Start;
  743. Break;
  744. end;
  745. end;
  746. end;
  747. except
  748. on E: Exception do begin
  749. if Assigned(LThread) then begin
  750. FreeAndNil(LThread);
  751. end;
  752. if Assigned(LPeer) then begin
  753. if not Assigned(LPeer.IOHandler) then begin
  754. FreeAndNil(LIOHandler);
  755. end;
  756. FreeAndNil(LPeer);
  757. end;
  758. Server.DoListenException(Self, E);
  759. end;
  760. end;
  761. End;
  762. { TIdTCPServerConnection }
  763. constructor TIdTCPServerConnection.Create(AServer: TIdTCPServer);
  764. begin
  765. inherited Create(nil);
  766. FServer := AServer;
  767. end;
  768. { TIdPeerThread }
  769. procedure TIdPeerThread.BeforeRun;
  770. begin
  771. try
  772. if Assigned(Connection.IOHandler) then begin
  773. Connection.IOHandler.AfterAccept;
  774. end
  775. else begin
  776. raise EIdTCPServerError.Create('');
  777. end;
  778. except
  779. Terminate; //APR: was FreeOn Terminate := True; ?! It is ThreadMgr work
  780. raise;
  781. end;
  782. if Assigned(Connection.Server.Intercept) then begin
  783. Connection.Intercept := Connection.Server.Intercept.Accept(Connection);
  784. end;
  785. Connection.Server.DoConnect(Self);
  786. // Stop this thread if we were disconnected
  787. if not Connection.Connected then begin
  788. Stop;
  789. end;
  790. end;
  791. procedure TIdPeerThread.AfterRun;
  792. begin
  793. with Connection.Server do begin
  794. DoDisconnect(Self);
  795. if Assigned(Connection.Server.Intercept) then begin
  796. Connection.Intercept.free;
  797. Connection.Intercept:=nil;
  798. end;
  799. end;
  800. end;
  801. procedure TIdPeerThread.Cleanup;
  802. begin
  803. inherited Cleanup;
  804. if Assigned(FConnection) then begin
  805. if Assigned(FConnection.Server) then begin
  806. { Remove is not neede if we are going to use only ActiveThreads; Threads.Remove(Self);}
  807. with Connection.Server do begin
  808. if Assigned(Threads) then begin
  809. Threads.Remove(SELF);
  810. end;
  811. //from AfterRun
  812. if Assigned(ThreadMgr) then begin
  813. ThreadMgr.ReleaseThread(Self);
  814. end;
  815. end;//with
  816. end;//if
  817. FreeAndNil(FConnection);
  818. end;
  819. // Other things are done in AfterExecute&destructor
  820. End;//TIdPeerThread.Cleanup
  821. procedure TIdPeerThread.Run;
  822. begin
  823. try
  824. try
  825. if not Connection.Server.DoExecute(Self) then begin
  826. raise EIdNoExecuteSpecified.Create(RSNoExecuteSpecified);
  827. end;
  828. except
  829. // We handle these seperate as after these we expect .Connected to be false
  830. // and caught below. Other exceptions are caught by the outer except.
  831. on E: EIdSocketError do begin
  832. Connection.Server.DoException(Self, E);
  833. case E.LastError of
  834. Id_WSAECONNABORTED // WSAECONNABORTED - Other side disconnected
  835. , Id_WSAECONNRESET:
  836. Connection.DisconnectSocket;
  837. end;
  838. end;
  839. on E: EIdClosedSocket do begin
  840. // No need to disconnect - this error means we are already disconnected or never connected
  841. Connection.Server.DoException(Self, E);
  842. end;
  843. on E: EIdConnClosedGracefully do begin
  844. // No need to Disconnect, .Connected will detect a graceful close
  845. Connection.Server.DoException(Self, E);
  846. end;
  847. end;
  848. // If connection lost, stop thread
  849. if not Connection.Connected then begin
  850. Stop;
  851. end;
  852. // Master catch. Catch errors not known about above, or errors in Stop, etc.
  853. // Must be a master catch to prevent thread from doing nothing.
  854. except
  855. on E: Exception do begin
  856. Connection.Server.DoException(Self, E);
  857. raise;
  858. end;
  859. end;
  860. end;
  861. { TIdCommandHandlers }
  862. function TIdCommandHandlers.Add: TIdCommandHandler;
  863. begin
  864. Result := TIdCommandHandler(inherited Add);
  865. end;
  866. constructor TIdCommandHandlers.Create(AServer: TIdTCPServer);
  867. begin
  868. inherited Create(AServer, TIdCommandHandler);
  869. FServer := AServer;
  870. end;
  871. function TIdCommandHandlers.GetItem(AIndex: Integer): TIdCommandHandler;
  872. begin
  873. Result := TIdCommandHandler(inherited Items[AIndex]);
  874. end;
  875. function TIdCommandHandlers.GetOwnedBy: TPersistent;
  876. begin
  877. Result := GetOwner;
  878. end;
  879. procedure TIdCommandHandlers.SetItem(AIndex: Integer; const AValue: TIdCommandHandler);
  880. begin
  881. inherited SetItem(AIndex, AValue);
  882. end;
  883. { TIdCommandHandler }
  884. function TIdCommandHandler.Check(const AData: string; AThread: TIdPeerThread): boolean;
  885. // AData is not preparsed and is completely left up to the command handler. This will allow for
  886. // future expansion such as wild cards etc, and allow the logic to properly remain in each of the
  887. // command handler implementations. In the future there may be a base type and multiple descendants
  888. var
  889. LUnparsedParams: string;
  890. begin
  891. LUnparsedParams := '';
  892. Result := AnsiSameText(AData, Command); // Command by itself
  893. if not Result then begin
  894. if CmdDelimiter <> #0 then begin
  895. Result := AnsiSameText(Copy(AData, 1, Length(Command) + 1), Command + CmdDelimiter);
  896. LUnparsedParams := Copy(AData, Length(Command) + 2, MaxInt);
  897. end else begin
  898. // Dont strip any part of the params out.. - just remove the command purely on length and
  899. // no delim
  900. Result := AnsiSameText(Copy(AData, 1, Length(Command)), Command);
  901. LUnparsedParams := Copy(AData, Length(Command) + 1, MaxInt);
  902. end;
  903. end;
  904. if Result then begin
  905. with TIdCommand.Create do try
  906. FRawLine := AData;
  907. FCommandHandler := Self;
  908. FThread := AThread;
  909. FUnparsedParams := LUnparsedParams;
  910. Params.Clear;
  911. if ParseParams then begin
  912. if Self.FParamDelimiter = #32 then begin
  913. SplitColumnsNoTrim(LUnparsedParams,Params,#32);
  914. end else begin
  915. SplitColumns(LUnparsedParams,Params,Self.FParamDelimiter);
  916. end;
  917. end;
  918. PerformReply := True;
  919. Reply.Assign(Self.ReplyNormal);
  920. while True do begin
  921. try
  922. DoCommand;
  923. except
  924. on E: Exception do begin
  925. if PerformReply then begin
  926. if Self.ReplyExceptionCode > 0 then begin
  927. Reply.SetReply(ReplyExceptionCode, E.Message);
  928. SendReply;
  929. end else if AThread.Connection.Server.ReplyExceptionCode > 0 then begin
  930. Reply.SetReply(AThread.Connection.Server.ReplyExceptionCode, E.Message);
  931. SendReply;
  932. end else begin
  933. raise;
  934. end;
  935. Break;
  936. end else begin
  937. raise;
  938. end;
  939. end;
  940. end;
  941. if PerformReply then begin
  942. SendReply;
  943. end;
  944. if Response.Count > 0 then begin
  945. AThread.Connection.WriteRFCStrings(Response);
  946. end else if CommandHandler.Response.Count > 0 then begin
  947. AThread.Connection.WriteRFCStrings(CommandHandler.Response);
  948. end;
  949. Break;
  950. end;
  951. finally
  952. try
  953. if Disconnect then begin
  954. AThread.Connection.Disconnect;
  955. end;
  956. finally
  957. FREE;
  958. end;
  959. end;//tryf
  960. end;
  961. end;
  962. constructor TIdCommandHandler.Create(ACollection: TCollection);
  963. begin
  964. inherited Create(ACollection);
  965. FCmdDelimiter := ' ';
  966. FEnabled := IdEnabledDefault;
  967. FName := ClassName + IntToStr(ID);
  968. FParamDelimiter := #32;
  969. FParseParams := IdParseParamsDefault;
  970. FReplyNormal := TIdRFCReply.Create(nil);
  971. FResponse := TStringList.Create;
  972. end;
  973. destructor TIdCommandHandler.Destroy;
  974. begin
  975. FreeAndNil(FResponse);
  976. FreeAndNil(FReplyNormal);
  977. inherited Destroy;
  978. end;
  979. function TIdCommandHandler.GetDisplayName: string;
  980. begin
  981. if Command = '' then begin
  982. Result := Name;
  983. end else begin
  984. Result := Command;
  985. end;
  986. end;
  987. function TIdCommandHandler.GetNamePath: string;
  988. begin
  989. if Collection <> nil then begin
  990. // OwnedBy is used because D4/D5 dont expose Owner on TOwnedCollection but D6 does
  991. Result := TIdCommandHandlers(Collection).OwnedBy.GetNamePath + '.' + Name;
  992. end else begin
  993. Result := inherited GetNamePath;
  994. end;
  995. end;
  996. function TIdCommandHandler.NameIs(ACommand: string): Boolean;
  997. begin
  998. Result := AnsiSameText(ACommand, FName);
  999. end;
  1000. procedure TIdCommandHandler.SetDisplayName(const AValue: string);
  1001. begin
  1002. FName := AValue;
  1003. inherited SetDisplayName(AValue);
  1004. end;
  1005. procedure TIdCommandHandler.SetReplyNormal(AValue: TIdRFCReply);
  1006. begin
  1007. FReplyNormal.Assign(AValue);
  1008. end;
  1009. procedure TIdCommandHandler.SetResponse(AValue: TStrings);
  1010. begin
  1011. FResponse.Assign(AValue);
  1012. end;
  1013. { TIdCommand }
  1014. constructor TIdCommand.Create;
  1015. begin
  1016. inherited Create;
  1017. FParams := TStringList.Create;
  1018. FReply := TIdRFCReply.Create(nil);
  1019. FResponse := TStringList.Create;
  1020. end;
  1021. destructor TIdCommand.Destroy;
  1022. begin
  1023. FreeAndNil(FReply);
  1024. FreeAndNil(FResponse);
  1025. FreeAndNil(FParams);
  1026. inherited Destroy;
  1027. end;
  1028. procedure TIdCommand.DoCommand;
  1029. begin
  1030. if Assigned(CommandHandler.OnCommand) then begin
  1031. CommandHandler.OnCommand(Self);
  1032. end;
  1033. end;
  1034. procedure TIdCommand.SendReply;
  1035. begin
  1036. PerformReply := False;
  1037. TIdCommandHandlers(CommandHandler.Collection).Server.ReplyTexts.UpdateText(Reply);
  1038. Thread.Connection.WriteRFCReply(Reply);
  1039. end;
  1040. procedure TIdCommand.SetReply(AValue: TIdRFCReply);
  1041. begin
  1042. FReply.Assign(AValue);
  1043. end;
  1044. procedure TIdCommand.SetResponse(AValue: TStrings);
  1045. begin
  1046. FResponse.Assign(AValue);
  1047. end;
  1048. end.