1 # README # |
1 # README # |
2 |
2 |
3 This README would normally document whatever steps are necessary to get your application up and running. |
3 ## Overview ## |
4 |
4 |
5 ### What is this repository for? ### |
5 This is a pure-nim client library for interacting with Stomp |
6 |
6 compliant messaging brokers. |
7 * Quick summary |
7 |
8 * Version |
8 https://stomp.github.io/ |
9 * [Learn Markdown](https://bitbucket.org/tutorials/markdowndemo) |
9 |
10 |
10 Stomp is a simple protocol for message passing between clients, using a central |
11 ### How do I get set up? ### |
11 broker. It is a subset of other more elaborate protocols (like AMQP), supporting |
12 |
12 only the most used features of common brokers. |
13 * Summary of set up |
13 |
14 * Configuration |
14 Because this library is pure-nim, there are no external dependencies. If you |
15 * Dependencies |
15 can compile a nim binary, you can participate in advanced messaging between processes. |
16 * Database configuration |
16 |
17 * How to run tests |
17 A list of broker support for Stomp can be found here: |
18 * Deployment instructions |
18 https://stomp.github.io/implementations.html. |
19 |
19 |
20 ### Contribution guidelines ### |
20 This library has been tested with recent versions of RabbitMQ. If it |
21 |
21 works for you with another broker, please let the author know. |
22 * Writing tests |
22 |
23 * Code review |
23 ### Installation ### |
24 * Other guidelines |
24 |
25 |
25 The easiest way to install this module is via the nimble package manager, |
26 ### Who do I talk to? ### |
26 by simply running 'nimble install stomp'. |
27 |
27 |
28 * Repo owner or admin |
28 Alternatively, you can fetch the 'stomp.nim' file yourself, and put it |
29 * Other community or team contact |
29 in a place of your choosing. |
|
30 |
|
31 |
|
32 ### Protocol support ### |
|
33 |
|
34 This library supports (almost) the entirety of the Stomp 1.2 spec, |
|
35 with the exception of client to server heartbeat. Server to client |
|
36 heartbeat is fully supported, which should normally be sufficient to |
|
37 keep firewall state tables open and sockets alive. |
|
38 |
|
39 |
|
40 ### Callbacks ### |
|
41 |
|
42 Because a client can receive frames at any time, most of the behavior |
|
43 of this module is implemented via callback procs. |
|
44 |
|
45 By default, most every event is a no-op. You can override various |
|
46 behaviors with the following callbacks: |
|
47 |
|
48 * **connected_callback**: Called when the Stomp library makes a successful |
|
49 connection to the broker. |
|
50 |
|
51 * **error_callback**: Called if there was a **ERROR** frame in the stream. By default, |
|
52 this raises a **StompError** exception with the error message. |
|
53 |
|
54 * **heartbeat_callback**: Called when a server heartbeat is received. |
|
55 |
|
56 * **message_callback**: Called when a **MESSAGE** frame is received. |
|
57 |
|
58 * **missed_heartbeat_callback**: Called when the Stomp socket is idle longer than |
|
59 the specified heartbeat time -- usually an indication of a problem. The default behavior |
|
60 raises a **StompError** exception. |
|
61 |
|
62 * **receipt_callback**: Called when a **RECEIPT** frame is received. |
|
63 |
|
64 |
|
65 ### Custom headers ### |
|
66 |
|
67 Depending on the broker, you may be able to add addtional features to outgoing messages |
|
68 by adding specific headers. You can also add "x-headers" that are carried between messages. |
|
69 |
|
70 Another use is to issue "receipts" on sends or subscriptions, to ensure the broker has |
|
71 processed your request. Here's an example of how to perform receipt processing: |
|
72 |
|
73 ``` |
|
74 #!nimrod |
|
75 proc accept_receipt( c: StompClient, r: StompResponse ) = |
|
76 var receipt = r[ "receipt-id" ] |
|
77 # ... match this receipt up to the request that generated it |
|
78 |
|
79 var client = newStompClient( socket, "..." ) |
|
80 |
|
81 client.receipt_callback = accept_receipt |
|
82 client.connect |
|
83 |
|
84 var headers = seq[ tuple[name:string, value:string] ] |
|
85 headers.add( "x-breakfast", "tequila" ) |
|
86 headers.add( "receipt", "special-identifier" ) |
|
87 |
|
88 client.send( "/destination", "message!", "text/plain", headers ) |
|
89 ``` |
|
90 |
|
91 |
|
92 ### Transactions ### |
|
93 |
|
94 This library has full support for transactions. Once entering a |
|
95 transaction, any messages or acknowledgments attached to it must be |
|
96 committed before the broker will release them. |
|
97 |
|
98 With one open transaction, messages are automatically attached to it. |
|
99 If you have multiple open transactions, you'll need to add which one |
|
100 you want a message to be part of via the custom headers. |
|
101 |
|
102 ``` |
|
103 #!nimrod |
|
104 # Single transaction |
|
105 # |
|
106 client.begin( "trans-1" ) |
|
107 client.send( "/destination", "hi" ) # Part of "trans-1" |
|
108 client.send( "/destination", "yo" ) # Part of "trans-1" |
|
109 client.send( "/destination", "whaddup" ) # Part of "trans-1" |
|
110 client.commit # or client.abort |
|
111 |
|
112 # Multiple simultaneous transactions |
|
113 # |
|
114 client.begin( "trans-1" ) |
|
115 client.begin( "trans-2" ) |
|
116 client.begin( "trans-3" ) |
|
117 |
|
118 var headers = seq[ tuple[name:string, value:string] ] |
|
119 headers.add( "transaction", "trans-1" ) |
|
120 client.send( "/destination", "hi", nil, headers ) # Part of "trans-1" |
|
121 |
|
122 headers = @[] |
|
123 headers.add( "transaction", "trans-2" ) |
|
124 client.send( "/destination", "hi", nil, headers ) # Part of "trans-2" |
|
125 client.ack( "some-ack-id", "trans-2" ) # Part of "trans-2" |
|
126 |
|
127 headers = @[] |
|
128 headers.add( "transaction", "trans-3" ) |
|
129 client.send( "/destination", "hi", nil, headers ) # Part of "trans-3" |
|
130 client.ack( "some-ack-id", "trans-3" ) # Part of "trans-3" |
|
131 |
|
132 client.abort( "trans-1" ) # anything "trans-1" never happened |
|
133 client.commit( "trans-2" ) # "trans-2" messages and acks are released |
|
134 client.abort( "trans-3" ) # anything "trans-3" never happened |
|
135 ``` |
|
136 |
|
137 ### Example ### |
|
138 |
|
139 This is a complete client that does the following: |
|
140 |
|
141 * Connect to an AMQP server at **mq.example.com** via SSL as the **test** user, |
|
142 in the **/example** vhost. |
|
143 * Request heartbeats every **5** seconds. |
|
144 * Subscribe to a topic exchange **events** with the key of **user.create**, requiring message |
|
145 acknowledgement. |
|
146 * Accept incoming messages, parsing the JSON payloads. |
|
147 * If parsing was successful, ACK the message and emit a new message to the exchange |
|
148 with JSON results to the **user.created** key -- presumably to be picked up by another |
|
149 process elsewhere. |
|
150 |
|
151 ``` |
|
152 #!nimrod |
|
153 # (This should be compiled with -d:ssl) |
|
154 |
|
155 import |
|
156 json, |
|
157 net, |
|
158 stomp |
|
159 |
|
160 let |
|
161 socket = newSocket() |
|
162 sslContext = newContext( verifyMode = CVerifyNone ) |
|
163 |
|
164 sslContext.wrapSocket( socket ) |
|
165 var client = newStompClient( socket, "stomp+ssl://test:test@mq.example.com/%2Fexample?heartbeat=5" ) |
|
166 |
|
167 # Announce when we're connected. |
|
168 proc connected( c: StompClient, r: StompResponse ) = |
|
169 echo "Connected to a ", c["server"], " server." |
|
170 |
|
171 # Echo to screen when we see a heartbeat. |
|
172 proc heartbeat( c: StompClient, r: StompResponse ) = |
|
173 echo "Heartbeat at: ", c.last_msgtime |
|
174 |
|
175 # Parse JSON, perform work, send success message. |
|
176 proc message( c: StompClient, r: StompResponse ) = |
|
177 let id = r[ "ack" ] |
|
178 |
|
179 if r[ "content-type" ] != "application/json": |
|
180 echo "I expect JSON payloads." |
|
181 c.nack( id ) |
|
182 return |
|
183 |
|
184 try: |
|
185 var json = r.payload.parse_json |
|
186 |
|
187 # ... do the heavy lifting with the parsed data. |
|
188 # ... and assuming is was successful, ack and emit! |
|
189 |
|
190 c.ack( id ) |
|
191 |
|
192 var message = %*{ "user": json["user"].getStr, "otherstuff": true } |
|
193 c.send( "/exchange/events/user.created", $message, "application/json" ) |
|
194 |
|
195 except JsonParsingError: |
|
196 echo "Couldn't parse JSON! ", r.payload |
|
197 c.nack( id ) |
|
198 |
|
199 # Attach callbacks |
|
200 client.connected_callback = connected |
|
201 client.message_callback = message |
|
202 client.heartbeat_callback = heartbeat |
|
203 |
|
204 # Open a session with the broker |
|
205 client.connect |
|
206 |
|
207 # Subscribe to a topic key, requiring acknowledgements. |
|
208 client.subscribe( "/exchange/events/user.create", "client-individual" ) |
|
209 |
|
210 # Enter message loop. |
|
211 client.wait_for_messages |
|
212 ``` |
|
213 |