PageRenderTime 56ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 0ms

/StompClient.pas

http://delphistompclient.googlecode.com/
Pascal | 602 lines | 471 code | 52 blank | 79 comment | 28 complexity | 8ab1e0911f8baadf9553e25578d52d4d MD5 | raw file
  1. // Stomp Client for Embarcadero Delphi & FreePascal
  2. // Tested With ApacheMQ 5.2/5.3, Apache Apollo 1.2
  3. // Copyright (c) 2009-2012 Daniele Teti
  4. //
  5. // Contributors:
  6. // Daniel Gaspary: dgaspary@gmail.com
  7. // Oliver Marr: oliver.sn@wmarr.de
  8. // WebSite: www.danieleteti.it
  9. // email:d.teti@bittime.it
  10. // *******************************************************
  11. unit StompClient;
  12. // For FreePascal users:
  13. // Automatically selected synapse tcp library
  14. {$IFDEF FPC}
  15. {$MODE DELPHI}
  16. {$DEFINE USESYNAPSE}
  17. {$ENDIF}
  18. // For Delphi users:
  19. // Decomment following line to use synapse also in Delphi
  20. { .$DEFINE USESYNAPSE }
  21. interface
  22. uses
  23. StompTypes,
  24. SysUtils,
  25. {$IFNDEF USESYNAPSE}
  26. IdTCPClient,
  27. IdException,
  28. IdExceptionCore,
  29. {$ELSE}
  30. synsock,
  31. blcksock,
  32. {$ENDIF}
  33. Classes;
  34. type
  35. { TStompClient }
  36. TStompClient = class(TInterfacedObject, IStompClient)
  37. private
  38. {$IFDEF USESYNAPSE}
  39. FSynapseTCP: TTCPBlockSocket;
  40. FSynapseConnected: boolean;
  41. {$ELSE}
  42. FTCP: TIdTCPClient;
  43. {$ENDIF}
  44. FHeaders: IStompHeaders;
  45. FPassword: string;
  46. FUserName: string;
  47. FTimeout: Integer;
  48. FSession: string;
  49. FInTransaction: boolean;
  50. FTransactions: TStringList;
  51. FReceiptTimeout: Integer;
  52. FServerProtocolVersion: string;
  53. FClientAcceptProtocolVersion: TStompAcceptProtocol;
  54. FServer: string;
  55. procedure SetReceiptTimeout(const Value: Integer);
  56. protected
  57. {$IFDEF USESYNAPSE}
  58. procedure SynapseSocketCallBack(Sender: TObject; Reason: THookSocketReason;
  59. const Value: string);
  60. {$ENDIF}
  61. procedure Init;
  62. procedure DeInit;
  63. procedure MergeHeaders(var AFrame: IStompFrame; var AHeaders: IStompHeaders);
  64. procedure SendFrame(AFrame: IStompFrame);
  65. public
  66. function SetPassword(const Value: string): IStompClient;
  67. function SetUserName(const Value: string): IStompClient;
  68. function Receive(out StompFrame: IStompFrame; ATimeout: Integer): boolean; overload;
  69. function Receive: IStompFrame; overload;
  70. function Receive(ATimeout: Integer): IStompFrame; overload;
  71. procedure Receipt(const ReceiptID: string);
  72. procedure Connect(Host: string = '127.0.0.1'; Port: Integer = DEFAULT_STOMP_PORT;
  73. ClientID: string = ''; AcceptVersion: TStompAcceptProtocol = STOMP_Version_1_0);
  74. procedure Disconnect;
  75. procedure Subscribe(QueueOrTopicName: string; Ack: TAckMode = amAuto;
  76. Headers: IStompHeaders = nil);
  77. procedure Unsubscribe(Queue: string);
  78. procedure Send(QueueOrTopicName: string; TextMessage: string; Headers: IStompHeaders = nil);
  79. overload;
  80. procedure Send(QueueOrTopicName: string; TextMessage: string; TransactionIdentifier: string;
  81. Headers: IStompHeaders = nil); overload;
  82. procedure Ack(const MessageID: string; const TransactionIdentifier: string = '');
  83. procedure BeginTransaction(const TransactionIdentifier: string);
  84. procedure CommitTransaction(const TransactionIdentifier: string);
  85. procedure AbortTransaction(const TransactionIdentifier: string);
  86. /// ////////////
  87. constructor Create; virtual;
  88. destructor Destroy; override;
  89. function Connected: boolean;
  90. function SetReceiveTimeout(const AMilliSeconds: Cardinal): IStompClient;
  91. function GetProtocolVersion: String;
  92. function GetServer: String;
  93. function GetSession: string;
  94. property ReceiptTimeout: Integer read FReceiptTimeout write SetReceiptTimeout;
  95. property Transactions: TStringList read FTransactions;
  96. end;
  97. implementation
  98. {$IFDEF FPC}
  99. const
  100. CHAR0 = #0;
  101. {$ELSE}
  102. uses
  103. Windows,
  104. IdGlobal,
  105. Character;
  106. {$ENDIF}
  107. { TStompClient }
  108. procedure TStompClient.AbortTransaction(const TransactionIdentifier: string);
  109. var
  110. Frame: IStompFrame;
  111. begin
  112. if FTransactions.IndexOf(TransactionIdentifier) > -1 then
  113. begin
  114. Frame := TStompFrame.Create;
  115. Frame.SetCommand('ABORT');
  116. Frame.GetHeaders.Add('transaction', TransactionIdentifier);
  117. SendFrame(Frame);
  118. FInTransaction := False;
  119. FTransactions.Delete(FTransactions.IndexOf(TransactionIdentifier));
  120. end
  121. else
  122. raise EStomp.CreateFmt('Abort Transaction Error. Transaction [%s] not found',
  123. [TransactionIdentifier]);
  124. end;
  125. procedure TStompClient.Ack(const MessageID: string; const TransactionIdentifier: string);
  126. var
  127. Frame: IStompFrame;
  128. begin
  129. Frame := TStompFrame.Create;
  130. Frame.SetCommand('ACK');
  131. Frame.GetHeaders.Add('message-id', MessageID);
  132. if TransactionIdentifier <> '' then
  133. Frame.GetHeaders.Add('transaction', TransactionIdentifier);
  134. SendFrame(Frame);
  135. end;
  136. procedure TStompClient.BeginTransaction(const TransactionIdentifier: string);
  137. var
  138. Frame: IStompFrame;
  139. begin
  140. if FTransactions.IndexOf(TransactionIdentifier) = -1 then
  141. begin
  142. Frame := TStompFrame.Create;
  143. Frame.SetCommand('BEGIN');
  144. Frame.GetHeaders.Add('transaction', TransactionIdentifier);
  145. SendFrame(Frame);
  146. // CheckReceipt(Frame);
  147. FInTransaction := True;
  148. FTransactions.Add(TransactionIdentifier);
  149. end
  150. else
  151. raise EStomp.CreateFmt('Begin Transaction Error. Transaction [%s] still open',
  152. [TransactionIdentifier]);
  153. end;
  154. // procedure TStompClient.CheckReceipt(Frame: TStompFrame);
  155. // var
  156. // ReceiptID: string;
  157. // begin
  158. // if FEnableReceipts then
  159. // begin
  160. // ReceiptID := inttostr(GetTickCount);
  161. // Frame.GetHeaders.Add('receipt', ReceiptID);
  162. // SendFrame(Frame);
  163. // Receipt(ReceiptID);
  164. // end
  165. // else
  166. // SendFrame(Frame);
  167. // end;
  168. procedure TStompClient.CommitTransaction(const TransactionIdentifier: string);
  169. var
  170. Frame: IStompFrame;
  171. begin
  172. if FTransactions.IndexOf(TransactionIdentifier) > -1 then
  173. begin
  174. Frame := TStompFrame.Create;
  175. Frame.SetCommand('COMMIT');
  176. Frame.GetHeaders.Add('transaction', TransactionIdentifier);
  177. SendFrame(Frame);
  178. FInTransaction := False;
  179. FTransactions.Delete(FTransactions.IndexOf(TransactionIdentifier));
  180. end
  181. else
  182. raise EStomp.CreateFmt('Commit Transaction Error. Transaction [%s] not found',
  183. [TransactionIdentifier]);
  184. end;
  185. procedure TStompClient.Connect(Host: string; Port: Integer; ClientID: string;
  186. AcceptVersion: TStompAcceptProtocol);
  187. var
  188. Frame: IStompFrame;
  189. begin
  190. try
  191. Init;
  192. {$IFDEF USESYNAPSE}
  193. FSynapseConnected := False;
  194. FSynapseTCP.Connect(Host, intToStr(Port));
  195. FSynapseConnected := True;
  196. {$ELSE}
  197. FTCP.Connect(Host, Port);
  198. FTCP.IOHandler.MaxLineLength := MaxInt;
  199. {$ENDIF}
  200. Frame := TStompFrame.Create;
  201. Frame.SetCommand('CONNECT');
  202. FClientAcceptProtocolVersion := AcceptVersion;
  203. if STOMP_Version_1_1 in [FClientAcceptProtocolVersion] then
  204. begin
  205. Frame.GetHeaders.Add('heart-beat', '0,1000'); // stomp 1.1
  206. Frame.GetHeaders.Add('accept-version', '1.1'); // stomp 1.1
  207. end;
  208. Frame.GetHeaders.Add('login', FUserName).Add('passcode', FPassword);
  209. if ClientID <> '' then
  210. Frame.GetHeaders.Add('client-id', ClientID);
  211. SendFrame(Frame);
  212. Frame := nil;
  213. while Frame = nil do
  214. Frame := Receive;
  215. if Frame.GetCommand = 'ERROR' then
  216. raise EStomp.Create(Frame.output);
  217. if Frame.GetCommand = 'CONNECTED' then
  218. begin
  219. FSession := Frame.GetHeaders.Value('session');
  220. FServerProtocolVersion := Frame.GetHeaders.Value('version'); // stomp 1.1
  221. FServer := Frame.GetHeaders.Value('server'); // stomp 1.1
  222. end;
  223. { todo: 'Call event?' }
  224. except
  225. on E: Exception do
  226. begin
  227. raise EStomp.Create(E.message);
  228. end;
  229. end;
  230. end;
  231. function TStompClient.Connected: boolean;
  232. begin
  233. {$IFDEF USESYNAPSE}
  234. Result := Assigned(FSynapseTCP) and FSynapseConnected;
  235. {$ELSE}
  236. Result := Assigned(FTCP) and FTCP.Connected;
  237. {$ENDIF}
  238. end;
  239. constructor TStompClient.Create;
  240. begin
  241. inherited;
  242. FInTransaction := False;
  243. FSession := '';
  244. FUserName := 'guest';
  245. FPassword := 'guest';
  246. FHeaders := TStompHeaders.Create;
  247. FTimeout := 200;
  248. FReceiptTimeout := FTimeout;
  249. end;
  250. procedure TStompClient.DeInit;
  251. begin
  252. {$IFDEF USESYNAPSE}
  253. FreeAndNil(FSynapseTCP);
  254. {$ELSE}
  255. FreeAndNil(FTCP);
  256. {$ENDIF}
  257. FreeAndNil(FTransactions);
  258. end;
  259. destructor TStompClient.Destroy;
  260. begin
  261. DeInit;
  262. inherited;
  263. end;
  264. procedure TStompClient.Disconnect;
  265. var
  266. Frame: IStompFrame;
  267. begin
  268. if Connected then
  269. begin
  270. Frame := TStompFrame.Create;
  271. Frame.SetCommand('DISCONNECT');
  272. SendFrame(Frame);
  273. {$IFDEF USESYNAPSE}
  274. FSynapseTCP.CloseSocket;
  275. FSynapseConnected := False;
  276. {$ELSE}
  277. FTCP.Disconnect;
  278. {$ENDIF}
  279. end;
  280. DeInit;
  281. end;
  282. function TStompClient.GetProtocolVersion: String;
  283. begin
  284. Result := FServerProtocolVersion;
  285. end;
  286. function TStompClient.GetServer: String;
  287. begin
  288. Result := FServer;
  289. end;
  290. function TStompClient.GetSession: string;
  291. begin
  292. Result := FSession;
  293. end;
  294. procedure TStompClient.Init;
  295. begin
  296. DeInit;
  297. {$IFDEF USESYNAPSE}
  298. FSynapseTCP := TTCPBlockSocket.Create;
  299. FSynapseTCP.OnStatus := SynapseSocketCallBack;
  300. FSynapseTCP.RaiseExcept := True;
  301. {$ELSE}
  302. FTCP := TIdTCPClient.Create(nil);
  303. {$ENDIF}
  304. FTransactions := TStringList.Create;
  305. end;
  306. {$IFDEF USESYNAPSE}
  307. procedure TStompClient.SynapseSocketCallBack(Sender: TObject;
  308. Reason: THookSocketReason; const Value: string);
  309. begin
  310. // As seen at TBlockSocket.ExceptCheck procedure, it SEEMS safe to say
  311. // when an error occurred and is not a Timeout, the connection is broken
  312. if (Reason = HR_Error) and (FSynapseTCP.LastError <> WSAETIMEDOUT)
  313. then
  314. begin
  315. FSynapseConnected := False;
  316. end;
  317. end;
  318. {$ENDIF}
  319. procedure TStompClient.MergeHeaders(var AFrame: IStompFrame; var AHeaders: IStompHeaders);
  320. var
  321. i: Integer;
  322. h: TKeyValue;
  323. begin
  324. if Assigned(AHeaders) then
  325. if AHeaders.Count > 0 then
  326. for i := 0 to AHeaders.Count - 1 do
  327. begin
  328. h := AHeaders.GetAt(i);
  329. AFrame.GetHeaders.Add(h.Key, h.Value);
  330. end;
  331. end;
  332. procedure TStompClient.Receipt(const ReceiptID: string);
  333. var
  334. Frame: IStompFrame;
  335. begin
  336. if Receive(Frame, FReceiptTimeout) then
  337. begin
  338. if Frame.GetCommand <> 'RECEIPT' then
  339. raise EStomp.Create('Receipt command error');
  340. if Frame.GetHeaders.Value('receipt-id') <> ReceiptID then
  341. raise EStomp.Create('Receipt receipt-id error');
  342. end;
  343. end;
  344. function TStompClient.Receive(out StompFrame: IStompFrame; ATimeout: Integer): boolean;
  345. begin
  346. StompFrame := nil;
  347. StompFrame := Receive(ATimeout);
  348. Result := Assigned(StompFrame);
  349. end;
  350. function TStompClient.Receive(ATimeout: Integer): IStompFrame;
  351. {$IFDEF USESYNAPSE}
  352. function InternalReceiveSynapse(ATimeout: Integer): IStompFrame;
  353. var
  354. c: char;
  355. s: string;
  356. tout: boolean;
  357. begin
  358. tout := False;
  359. Result := nil;
  360. try
  361. try
  362. FSynapseTCP.SetRecvTimeout(ATimeout);
  363. s := '';
  364. try
  365. while True do
  366. begin
  367. c := Chr(FSynapseTCP.RecvByte(ATimeout));
  368. if c <> CHAR0 then
  369. s := s + c // should be improved with a string buffer (daniele.teti)
  370. else
  371. begin
  372. c := Chr(FSynapseTCP.RecvByte(ATimeout));
  373. Break;
  374. end;
  375. end;
  376. except
  377. on E: ESynapseError do
  378. begin
  379. if E.ErrorCode = WSAETIMEDOUT then
  380. tout := True
  381. else
  382. raise;
  383. end;
  384. on E: Exception do
  385. begin
  386. raise;
  387. end;
  388. end;
  389. if not tout then
  390. begin
  391. Result := StompUtils.CreateFrame(s + CHAR0);
  392. end;
  393. finally
  394. s := '';
  395. end;
  396. except
  397. on E: Exception do
  398. begin
  399. raise;
  400. end;
  401. end;
  402. end;
  403. {$ELSE}
  404. function InternalReceiveINDY(ATimeout: Integer): IStompFrame;
  405. var
  406. c: char;
  407. sb: TStringBuilder;
  408. tout: boolean;
  409. FirstValidChar: boolean;
  410. UTF8Encoding: TEncoding;
  411. begin
  412. UTF8Encoding := TEncoding.UTF8;
  413. tout := False;
  414. Result := nil;
  415. try
  416. sb := TStringBuilder.Create(1024 * 4);
  417. try
  418. FTCP.ReadTimeout := ATimeout;
  419. try
  420. FirstValidChar := False;
  421. FTCP.Socket.CheckForDataOnSource(1);
  422. while True do
  423. begin
  424. c := FTCP.Socket.ReadChar(UTF8Encoding);
  425. if (c = LF) and (not FirstValidChar) then
  426. Continue;
  427. FirstValidChar := True;
  428. if c <> CHAR0 then
  429. sb.Append(c)
  430. else
  431. begin
  432. // FTCP.IOHandler.ReadChar(TEncoding.UTF8);
  433. Break;
  434. end;
  435. end;
  436. except
  437. on E: EIdReadTimeout do
  438. begin
  439. tout := True;
  440. end;
  441. on E: Exception do
  442. begin
  443. if sb.Length > 0 then
  444. raise EStomp.Create(E.message + sLineBreak + sb.toString)
  445. else
  446. raise;
  447. end;
  448. end;
  449. if not tout then
  450. begin
  451. Result := StompUtils.CreateFrame(sb.toString + CHAR0);
  452. if Result.GetCommand = 'ERROR' then
  453. raise EStomp.Create(Result.GetHeaders.Value('message'));
  454. end;
  455. finally
  456. sb.Free;
  457. end;
  458. except
  459. on E: Exception do
  460. begin
  461. raise;
  462. end;
  463. end;
  464. end;
  465. {$ENDIF}
  466. begin
  467. {$IFDEF USESYNAPSE}
  468. Result := InternalReceiveSynapse(ATimeout);
  469. {$ELSE}
  470. Result := InternalReceiveINDY(ATimeout);
  471. {$ENDIF}
  472. end;
  473. function TStompClient.Receive: IStompFrame;
  474. begin
  475. Result := Receive(FTimeout);
  476. end;
  477. procedure TStompClient.Send(QueueOrTopicName: string; TextMessage: string; Headers: IStompHeaders);
  478. var
  479. Frame: IStompFrame;
  480. begin
  481. Frame := TStompFrame.Create;
  482. Frame.SetCommand('SEND');
  483. Frame.GetHeaders.Add('destination', QueueOrTopicName);
  484. Frame.SetBody(TextMessage);
  485. MergeHeaders(Frame, Headers);
  486. SendFrame(Frame);
  487. end;
  488. procedure TStompClient.Send(QueueOrTopicName: string; TextMessage: string;
  489. TransactionIdentifier: string; Headers: IStompHeaders);
  490. var
  491. Frame: IStompFrame;
  492. begin
  493. Frame := TStompFrame.Create;
  494. Frame.SetCommand('SEND');
  495. Frame.GetHeaders.Add('destination', QueueOrTopicName);
  496. Frame.GetHeaders.Add('transaction', TransactionIdentifier);
  497. Frame.SetBody(TextMessage);
  498. MergeHeaders(Frame, Headers);
  499. SendFrame(Frame);
  500. end;
  501. procedure TStompClient.SendFrame(AFrame: IStompFrame);
  502. begin
  503. {$IFDEF USESYNAPSE}
  504. FSynapseTCP.SendString(AFrame.output);
  505. {$ELSE}
  506. // FTCP.IOHandler.write(TEncoding.ASCII.GetBytes(AFrame.output));
  507. FTCP.IOHandler.write(TEncoding.UTF8.GetBytes(AFrame.output));
  508. {$ENDIF}
  509. end;
  510. function TStompClient.SetPassword(const Value: string): IStompClient;
  511. begin
  512. FPassword := Value;
  513. Result := Self;
  514. end;
  515. procedure TStompClient.SetReceiptTimeout(const Value: Integer);
  516. begin
  517. FReceiptTimeout := Value;
  518. end;
  519. function TStompClient.SetReceiveTimeout(const AMilliSeconds: Cardinal): IStompClient;
  520. begin
  521. FTimeout := AMilliSeconds;
  522. Result := Self;
  523. end;
  524. function TStompClient.SetUserName(const Value: string): IStompClient;
  525. begin
  526. FUserName := Value;
  527. Result := Self;
  528. end;
  529. procedure TStompClient.Subscribe(QueueOrTopicName: string; Ack: TAckMode = amAuto;
  530. Headers: IStompHeaders = nil);
  531. var
  532. Frame: IStompFrame;
  533. begin
  534. Frame := TStompFrame.Create;
  535. Frame.SetCommand('SUBSCRIBE');
  536. Frame.GetHeaders.Add('destination', QueueOrTopicName).Add('ack', StompUtils.AckModeToStr(Ack));
  537. if Headers <> nil then
  538. MergeHeaders(Frame, Headers);
  539. SendFrame(Frame);
  540. end;
  541. procedure TStompClient.Unsubscribe(Queue: string);
  542. var
  543. Frame: IStompFrame;
  544. begin
  545. Frame := TStompFrame.Create;
  546. Frame.SetCommand('UNSUBSCRIBE');
  547. Frame.GetHeaders.Add('destination', Queue);
  548. SendFrame(Frame);
  549. end;
  550. end.