2016-03-25 21:42:44 +00:00
|
|
|
# README #
|
|
|
|
|
|
2016-03-25 15:18:09 -07:00
|
|
|
## Overview ##
|
2016-03-25 21:42:44 +00:00
|
|
|
|
2016-03-25 15:18:09 -07:00
|
|
|
This is a pure-nim client library for interacting with Stomp
|
|
|
|
|
compliant messaging brokers.
|
2016-03-25 21:42:44 +00:00
|
|
|
|
2016-03-25 15:18:09 -07:00
|
|
|
https://stomp.github.io/
|
2016-03-25 21:42:44 +00:00
|
|
|
|
2016-03-25 15:18:09 -07:00
|
|
|
Stomp is a simple protocol for message passing between clients, using a central
|
|
|
|
|
broker. It is a subset of other more elaborate protocols (like AMQP), supporting
|
|
|
|
|
only the most used features of common brokers.
|
2016-03-25 21:42:44 +00:00
|
|
|
|
2016-03-25 15:18:09 -07:00
|
|
|
Because this library is pure-nim, there are no external dependencies. If you
|
|
|
|
|
can compile a nim binary, you can participate in advanced messaging between processes.
|
2016-03-25 21:42:44 +00:00
|
|
|
|
2016-03-25 15:18:09 -07:00
|
|
|
A list of broker support for Stomp can be found here:
|
|
|
|
|
https://stomp.github.io/implementations.html.
|
2016-03-25 21:42:44 +00:00
|
|
|
|
2016-03-25 15:18:09 -07:00
|
|
|
This library has been tested with recent versions of RabbitMQ. If it
|
|
|
|
|
works for you with another broker, please let the author know.
|
2016-03-25 21:42:44 +00:00
|
|
|
|
2016-03-25 15:18:09 -07:00
|
|
|
### Installation ###
|
|
|
|
|
|
|
|
|
|
The easiest way to install this module is via the nimble package manager,
|
|
|
|
|
by simply running 'nimble install stomp'.
|
|
|
|
|
|
|
|
|
|
Alternatively, you can fetch the 'stomp.nim' file yourself, and put it
|
|
|
|
|
in a place of your choosing.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### Protocol support ###
|
|
|
|
|
|
|
|
|
|
This library supports (almost) the entirety of the Stomp 1.2 spec,
|
|
|
|
|
with the exception of client to server heartbeat. Server to client
|
|
|
|
|
heartbeat is fully supported, which should normally be sufficient to
|
|
|
|
|
keep firewall state tables open and sockets alive.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### Callbacks ###
|
|
|
|
|
|
|
|
|
|
Because a client can receive frames at any time, most of the behavior
|
|
|
|
|
of this module is implemented via callback procs.
|
|
|
|
|
|
|
|
|
|
By default, most every event is a no-op. You can override various
|
|
|
|
|
behaviors with the following callbacks:
|
|
|
|
|
|
|
|
|
|
* **connected_callback**: Called when the Stomp library makes a successful
|
|
|
|
|
connection to the broker.
|
|
|
|
|
|
|
|
|
|
* **error_callback**: Called if there was a **ERROR** frame in the stream. By default,
|
|
|
|
|
this raises a **StompError** exception with the error message.
|
|
|
|
|
|
|
|
|
|
* **heartbeat_callback**: Called when a server heartbeat is received.
|
|
|
|
|
|
|
|
|
|
* **message_callback**: Called when a **MESSAGE** frame is received.
|
|
|
|
|
|
|
|
|
|
* **missed_heartbeat_callback**: Called when the Stomp socket is idle longer than
|
|
|
|
|
the specified heartbeat time -- usually an indication of a problem. The default behavior
|
|
|
|
|
raises a **StompError** exception.
|
|
|
|
|
|
|
|
|
|
* **receipt_callback**: Called when a **RECEIPT** frame is received.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### Custom headers ###
|
|
|
|
|
|
|
|
|
|
Depending on the broker, you may be able to add addtional features to outgoing messages
|
|
|
|
|
by adding specific headers. You can also add "x-headers" that are carried between messages.
|
|
|
|
|
|
|
|
|
|
Another use is to issue "receipts" on sends or subscriptions, to ensure the broker has
|
|
|
|
|
processed your request. Here's an example of how to perform receipt processing:
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
#!nimrod
|
|
|
|
|
proc accept_receipt( c: StompClient, r: StompResponse ) =
|
|
|
|
|
var receipt = r[ "receipt-id" ]
|
|
|
|
|
# ... match this receipt up to the request that generated it
|
|
|
|
|
|
|
|
|
|
var client = newStompClient( socket, "..." )
|
|
|
|
|
|
|
|
|
|
client.receipt_callback = accept_receipt
|
|
|
|
|
client.connect
|
|
|
|
|
|
|
|
|
|
var headers = seq[ tuple[name:string, value:string] ]
|
2016-03-25 15:59:20 -07:00
|
|
|
headers.add( ("x-breakfast", "tequila") )
|
|
|
|
|
headers.add( ("receipt", "special-identifier") )
|
2016-03-25 15:18:09 -07:00
|
|
|
|
|
|
|
|
client.send( "/destination", "message!", "text/plain", headers )
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### Transactions ###
|
|
|
|
|
|
|
|
|
|
This library has full support for transactions. Once entering a
|
|
|
|
|
transaction, any messages or acknowledgments attached to it must be
|
|
|
|
|
committed before the broker will release them.
|
|
|
|
|
|
|
|
|
|
With one open transaction, messages are automatically attached to it.
|
|
|
|
|
If you have multiple open transactions, you'll need to add which one
|
|
|
|
|
you want a message to be part of via the custom headers.
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
#!nimrod
|
|
|
|
|
# Single transaction
|
|
|
|
|
#
|
|
|
|
|
client.begin( "trans-1" )
|
|
|
|
|
client.send( "/destination", "hi" ) # Part of "trans-1"
|
|
|
|
|
client.send( "/destination", "yo" ) # Part of "trans-1"
|
|
|
|
|
client.send( "/destination", "whaddup" ) # Part of "trans-1"
|
|
|
|
|
client.commit # or client.abort
|
|
|
|
|
|
|
|
|
|
# Multiple simultaneous transactions
|
|
|
|
|
#
|
|
|
|
|
client.begin( "trans-1" )
|
|
|
|
|
client.begin( "trans-2" )
|
|
|
|
|
client.begin( "trans-3" )
|
|
|
|
|
|
|
|
|
|
var headers = seq[ tuple[name:string, value:string] ]
|
2016-03-25 15:59:20 -07:00
|
|
|
headers.add( ("transaction", "trans-1") )
|
2016-03-25 15:18:09 -07:00
|
|
|
client.send( "/destination", "hi", nil, headers ) # Part of "trans-1"
|
|
|
|
|
|
|
|
|
|
headers = @[]
|
2016-03-25 15:59:20 -07:00
|
|
|
headers.add( ("transaction", "trans-2") )
|
2016-03-25 15:18:09 -07:00
|
|
|
client.send( "/destination", "hi", nil, headers ) # Part of "trans-2"
|
|
|
|
|
client.ack( "some-ack-id", "trans-2" ) # Part of "trans-2"
|
|
|
|
|
|
|
|
|
|
headers = @[]
|
2016-03-25 15:59:20 -07:00
|
|
|
headers.add( ("transaction", "trans-3") )
|
2016-03-25 15:18:09 -07:00
|
|
|
client.send( "/destination", "hi", nil, headers ) # Part of "trans-3"
|
|
|
|
|
client.ack( "some-ack-id", "trans-3" ) # Part of "trans-3"
|
|
|
|
|
|
|
|
|
|
client.abort( "trans-1" ) # anything "trans-1" never happened
|
|
|
|
|
client.commit( "trans-2" ) # "trans-2" messages and acks are released
|
|
|
|
|
client.abort( "trans-3" ) # anything "trans-3" never happened
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Example ###
|
|
|
|
|
|
|
|
|
|
This is a complete client that does the following:
|
|
|
|
|
|
|
|
|
|
* Connect to an AMQP server at **mq.example.com** via SSL as the **test** user,
|
|
|
|
|
in the **/example** vhost.
|
|
|
|
|
* Request heartbeats every **5** seconds.
|
|
|
|
|
* Subscribe to a topic exchange **events** with the key of **user.create**, requiring message
|
|
|
|
|
acknowledgement.
|
|
|
|
|
* Accept incoming messages, parsing the JSON payloads.
|
|
|
|
|
* If parsing was successful, ACK the message and emit a new message to the exchange
|
|
|
|
|
with JSON results to the **user.created** key -- presumably to be picked up by another
|
|
|
|
|
process elsewhere.
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
#!nimrod
|
|
|
|
|
# (This should be compiled with -d:ssl)
|
|
|
|
|
|
|
|
|
|
import
|
2021-10-27 14:36:25 -07:00
|
|
|
std/[net,json],
|
2016-03-25 15:18:09 -07:00
|
|
|
stomp
|
|
|
|
|
|
|
|
|
|
let
|
|
|
|
|
socket = newSocket()
|
|
|
|
|
sslContext = newContext( verifyMode = CVerifyNone )
|
|
|
|
|
|
|
|
|
|
sslContext.wrapSocket( socket )
|
|
|
|
|
var client = newStompClient( socket, "stomp+ssl://test:test@mq.example.com/%2Fexample?heartbeat=5" )
|
|
|
|
|
|
|
|
|
|
# Announce when we're connected.
|
|
|
|
|
proc connected( c: StompClient, r: StompResponse ) =
|
|
|
|
|
echo "Connected to a ", c["server"], " server."
|
|
|
|
|
|
|
|
|
|
# Echo to screen when we see a heartbeat.
|
|
|
|
|
proc heartbeat( c: StompClient, r: StompResponse ) =
|
|
|
|
|
echo "Heartbeat at: ", c.last_msgtime
|
|
|
|
|
|
|
|
|
|
# Parse JSON, perform work, send success message.
|
|
|
|
|
proc message( c: StompClient, r: StompResponse ) =
|
|
|
|
|
let id = r[ "ack" ]
|
|
|
|
|
|
|
|
|
|
if r[ "content-type" ] != "application/json":
|
|
|
|
|
echo "I expect JSON payloads."
|
|
|
|
|
c.nack( id )
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
var json = r.payload.parse_json
|
|
|
|
|
|
|
|
|
|
# ... do the heavy lifting with the parsed data.
|
|
|
|
|
# ... and assuming is was successful, ack and emit!
|
|
|
|
|
|
|
|
|
|
c.ack( id )
|
|
|
|
|
|
|
|
|
|
var message = %*{ "user": json["user"].getStr, "otherstuff": true }
|
|
|
|
|
c.send( "/exchange/events/user.created", $message, "application/json" )
|
|
|
|
|
|
|
|
|
|
except JsonParsingError:
|
|
|
|
|
echo "Couldn't parse JSON! ", r.payload
|
|
|
|
|
c.nack( id )
|
|
|
|
|
|
|
|
|
|
# Attach callbacks
|
|
|
|
|
client.connected_callback = connected
|
|
|
|
|
client.message_callback = message
|
|
|
|
|
client.heartbeat_callback = heartbeat
|
|
|
|
|
|
|
|
|
|
# Open a session with the broker
|
|
|
|
|
client.connect
|
|
|
|
|
|
|
|
|
|
# Subscribe to a topic key, requiring acknowledgements.
|
|
|
|
|
client.subscribe( "/exchange/events/user.create", "client-individual" )
|
|
|
|
|
|
|
|
|
|
# Enter message loop.
|
|
|
|
|
client.wait_for_messages
|
|
|
|
|
```
|
2016-03-25 21:42:44 +00:00
|
|
|
|