author | Mahlon E. Smith <mahlon@martini.nu> |
Wed, 27 Oct 2021 14:36:25 -0700 | |
changeset 10 | d63cce6d1a09 |
parent 3 | d0bc42746346 |
permissions | -rw-r--r-- |
1
3c678f7bf1a0
README.md edited online with Bitbucket
Mahlon Smith <mahlon@martini.nu>
parents:
diff
changeset
|
1 |
# README # |
3c678f7bf1a0
README.md edited online with Bitbucket
Mahlon Smith <mahlon@martini.nu>
parents:
diff
changeset
|
2 |
|
2 | 3 |
## Overview ## |
4 |
||
5 |
This is a pure-nim client library for interacting with Stomp |
|
6 |
compliant messaging brokers. |
|
7 |
||
8 |
https://stomp.github.io/ |
|
9 |
||
10 |
Stomp is a simple protocol for message passing between clients, using a central |
|
11 |
broker. It is a subset of other more elaborate protocols (like AMQP), supporting |
|
12 |
only the most used features of common brokers. |
|
13 |
||
14 |
Because this library is pure-nim, there are no external dependencies. If you |
|
15 |
can compile a nim binary, you can participate in advanced messaging between processes. |
|
16 |
||
17 |
A list of broker support for Stomp can be found here: |
|
18 |
https://stomp.github.io/implementations.html. |
|
19 |
||
20 |
This library has been tested with recent versions of RabbitMQ. If it |
|
21 |
works for you with another broker, please let the author know. |
|
22 |
||
23 |
### Installation ### |
|
1
3c678f7bf1a0
README.md edited online with Bitbucket
Mahlon Smith <mahlon@martini.nu>
parents:
diff
changeset
|
24 |
|
2 | 25 |
The easiest way to install this module is via the nimble package manager, |
26 |
by simply running 'nimble install stomp'. |
|
27 |
||
28 |
Alternatively, you can fetch the 'stomp.nim' file yourself, and put it |
|
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. |
|
1
3c678f7bf1a0
README.md edited online with Bitbucket
Mahlon Smith <mahlon@martini.nu>
parents:
diff
changeset
|
50 |
|
2 | 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: |
|
1
3c678f7bf1a0
README.md edited online with Bitbucket
Mahlon Smith <mahlon@martini.nu>
parents:
diff
changeset
|
72 |
|
2 | 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] ] |
|
3 | 85 |
headers.add( ("x-breakfast", "tequila") ) |
86 |
headers.add( ("receipt", "special-identifier") ) |
|
2 | 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. |
|
1
3c678f7bf1a0
README.md edited online with Bitbucket
Mahlon Smith <mahlon@martini.nu>
parents:
diff
changeset
|
101 |
|
2 | 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] ] |
|
3 | 119 |
headers.add( ("transaction", "trans-1") ) |
2 | 120 |
client.send( "/destination", "hi", nil, headers ) # Part of "trans-1" |
121 |
||
122 |
headers = @[] |
|
3 | 123 |
headers.add( ("transaction", "trans-2") ) |
2 | 124 |
client.send( "/destination", "hi", nil, headers ) # Part of "trans-2" |
125 |
client.ack( "some-ack-id", "trans-2" ) # Part of "trans-2" |
|
1
3c678f7bf1a0
README.md edited online with Bitbucket
Mahlon Smith <mahlon@martini.nu>
parents:
diff
changeset
|
126 |
|
2 | 127 |
headers = @[] |
3 | 128 |
headers.add( ("transaction", "trans-3") ) |
2 | 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) |
|
1
3c678f7bf1a0
README.md edited online with Bitbucket
Mahlon Smith <mahlon@martini.nu>
parents:
diff
changeset
|
154 |
|
2 | 155 |
import |
10
d63cce6d1a09
Updates for more modern (v1.6.0) nim.
Mahlon E. Smith <mahlon@martini.nu>
parents:
3
diff
changeset
|
156 |
std/[net,json], |
2 | 157 |
stomp |
158 |
||
159 |
let |
|
160 |
socket = newSocket() |
|
161 |
sslContext = newContext( verifyMode = CVerifyNone ) |
|
162 |
||
163 |
sslContext.wrapSocket( socket ) |
|
164 |
var client = newStompClient( socket, "stomp+ssl://test:test@mq.example.com/%2Fexample?heartbeat=5" ) |
|
165 |
||
166 |
# Announce when we're connected. |
|
167 |
proc connected( c: StompClient, r: StompResponse ) = |
|
168 |
echo "Connected to a ", c["server"], " server." |
|
169 |
||
170 |
# Echo to screen when we see a heartbeat. |
|
171 |
proc heartbeat( c: StompClient, r: StompResponse ) = |
|
172 |
echo "Heartbeat at: ", c.last_msgtime |
|
173 |
||
174 |
# Parse JSON, perform work, send success message. |
|
175 |
proc message( c: StompClient, r: StompResponse ) = |
|
176 |
let id = r[ "ack" ] |
|
177 |
||
178 |
if r[ "content-type" ] != "application/json": |
|
179 |
echo "I expect JSON payloads." |
|
180 |
c.nack( id ) |
|
181 |
return |
|
1
3c678f7bf1a0
README.md edited online with Bitbucket
Mahlon Smith <mahlon@martini.nu>
parents:
diff
changeset
|
182 |
|
2 | 183 |
try: |
184 |
var json = r.payload.parse_json |
|
185 |
||
186 |
# ... do the heavy lifting with the parsed data. |
|
187 |
# ... and assuming is was successful, ack and emit! |
|
188 |
||
189 |
c.ack( id ) |
|
190 |
||
191 |
var message = %*{ "user": json["user"].getStr, "otherstuff": true } |
|
192 |
c.send( "/exchange/events/user.created", $message, "application/json" ) |
|
193 |
||
194 |
except JsonParsingError: |
|
195 |
echo "Couldn't parse JSON! ", r.payload |
|
196 |
c.nack( id ) |
|
1
3c678f7bf1a0
README.md edited online with Bitbucket
Mahlon Smith <mahlon@martini.nu>
parents:
diff
changeset
|
197 |
|
2 | 198 |
# Attach callbacks |
199 |
client.connected_callback = connected |
|
200 |
client.message_callback = message |
|
201 |
client.heartbeat_callback = heartbeat |
|
202 |
||
203 |
# Open a session with the broker |
|
204 |
client.connect |
|
205 |
||
206 |
# Subscribe to a topic key, requiring acknowledgements. |
|
207 |
client.subscribe( "/exchange/events/user.create", "client-individual" ) |
|
208 |
||
209 |
# Enter message loop. |
|
210 |
client.wait_for_messages |
|
211 |
``` |
|
212 |