# A Narrative for Users This document explores the `OnStomp` API through a narrative aimed at end users of the library. It will start with the basics and work through the available features through exposition and examples. It may be helpful to review the [STOMP specification](http://stomp.github.com/index.html) before diving into this document. It's also important to note that `onstomp` can only be used with Ruby 1.8.7+. Support for Rubies prior to 1.8.7 does not exist, and even requiring the library in your code will probably generate errors. ## Creating a STOMP Client Creating a {OnStomp::Client client} connection to a STOMP broker is done by creating a new client and connecting it. This can be accomplished a few different ways. !!!ruby require 'onstomp' # The common way client = OnStomp::Client.new("stomp://host.example.org") client.connect # A short-cut client = OnStomp.connect "stomp://host.example.org" The {OnStomp.connect} method creates a new client instance and immediately calls {OnStomp::Client#connect connect} on it. This method is also aliased as `open`, so use the verbiage you're most comfortable with. Once connected, frames can be sent to the STOMP broker through a series of convenient (and fairly common amongst most STOMP clients) methods such as {OnStomp::Interfaces::FrameMethods#send send}, {OnStomp::Interfaces::FrameMethods#subscribe subscribe} and {OnStomp::Interfaces::FrameMethods#ack ack}. A full list of the frame methods can be found in the documentation for the {OnStomp::Interfaces::FrameMethods} mixin. So, let's send some SEND frame to the broker: !!!ruby client.send '/queue/test', 'Hello World!' client.send '/queue/test', 'Persist this, please.', :persistent => true Most frame-generating methods treat the last parameter as a hash of headers to include with the generated frame. The only exception to this is the {OnStomp::Interfaces::FrameMethods#beat heart-beat} frame, which has no command, headers or body. ## Subscriptions and Receipts ### Subscribing: Send me stuff, and maybe I'll tell you when I got it. Subscriptions in `onstomp` are pretty much just blocks that get called every time a MESSAGE frame is received that matches a previously sent SUBSCRIBE frame. To set up a subscription, just pass a block to the {OnStomp::Interfaces::FrameMethods#subscribe subscribe} method: !!!ruby client.subscribe '/queue/test' do |msg| # Invoked every time the broker delivers a MESSAGE frame for the # SUBSCRIBE frame generated by this method call. puts "Got a message: #{msg.body}" end The STOMP protocol supports a few different ways of acknowledging that MESSAGE frames were received, depending upon the protocol version. STOMP 1.0 connections support automatic acknowledgment (the default behavior) and client-side message acknowledgment. STOMP 1.1 adds a `client-individual` mode that may behave differently depending upon the broker you are using. It is considered incorrect for a client to acknowledge MESSAGE frames with ACK frames if the subscription is operating in `auto` mode. To set the ack mode of a subscription, include an `:ack` header in your call to {OnStomp::Interfaces::FrameMethods#subscribe subscribe}: !!!ruby # Technically, this isn't needed as auto is the default ack mode client.subscribe '/queue/test', :ack => 'auto' do |msg| # process the MESSAGE frame # ... end # Set the subscription's ack mode to client client.subscribe '/queue/test', :ack => 'client' do |msg| # process the MESSAGE frame # ... # Tell the broker that the MESSAGE frame was processed client.ack msg end # Set the subscription's ack mode to client-individual client.subscribe '/queue/test', :ack => 'client-individual' do |msg| # process the MESSAGE frame # ... # Tell the broker that the MESSAGE frame was NOT processed client.nack msg end The difference between `:ack => 'client'` and `:ack => 'client-individual` largely depends upon the STOMP broker. Apache's [ActiveMQ](http://activemq.apache.org/) treats ACK frames received for a MESSAGE frame as "cumulative acknowledgements," that is an ACK frame acknowledges the MESSAGE it was sent for and all previous MESSAGE frames sent from the broker to the client. The STOMP 1.1 spec clarified the expected behavior brokers should exhibit when receiving an ACK frame, and a `client-individual` ack mode specifies that each MESSAGE frame will be acknowledged with its own ACK (or NACK) frame. There may be brokers that behave this way when using a `client` ack mode, so what happens when you ACK a MESSAGE in `client` mode depends heavily on the broker being used. The NACK frame was introduced in the STOMP 1.1 spec and gives the client a way to tell the broker that it did not successfully process a MESSAGE. It is very similar in structure to an ACK frame, which makes sense it is little more than a "Not ACK". ### Receipts: Did you get that thing I sent you? Most frames a client sends to a STOMP broker can be receipted (ie: the client can instruct the broker to send a RECEIPT frame after it receives the original frame.) The two exceptions to this are heart-beat frames (as mentioned previously, heart-beats really aren't frames) and CONNECT frames. The client does this by including a `receipt` header that specifies an receipt ID for the frame being sent, the broker will in turn deliver a RECEIPT frame with a matching `receipt-id` header. In OnStomp, requesting a receipt for a SEND frame is as easy as including a block with your call to {OnStomp::Interfaces::FrameMethods#send send}: !!!ruby client.send '/queue/test', 'Did you get this?' do |r| puts "Got my receipt: #{r[:'receipt-id']}" end To request receipts for other types of frames, see {file:docs/UserNarrative.md#with\_receipt with\_receipt} subsection of {file:docs/UserNarrative.md#Scopes Scopes}. ## Scopes Sometimes you want to do the same stuff with a series of frames, and that's why we have {OnStomp::Components::Scopes scopes}. ### with_headers A {OnStomp::Components::Scopes::HeaderScope header} scope is a convenient way to apply a common set of headers to a series of frames. You can create a new header scope from a client by calling {OnStomp::Components::Scopes#with\_headers with\_headers}: !!!ruby scope = client.with_headers :persistent => true, :'content-type' => 'text/plain' scope.send '/queue/test', 'walks in to the room' scope.send '/queue/test', 'feels like a big balloon', :persitent => false scope.send '/queue/test', 'big girls, you are beautiful' All of the SEND frames generated will have a `content-type` header with a value of `text/plain`. The first and last frames will also have a header of `persistent` with a value of `true`; however, the middle frame's `persistent` header will have a value of `false`. As illustrated, headers specified on the frame-generating methods will override those defined for the scope. If you want to apply the headers to a series of frames in one swoop and don't need to keep a `scope` variable around, you can pass a block to `with_headers`: !!!ruby client.with_headers :persistent => true, :'content-type' => 'text/plain' do |h| h.send '/queue/test', 'walks in to the room' h.send '/queue/test', 'feels like a big balloon', :persitent => false h.send '/queue/test', 'big girls, you are beautiful' end This code sample produces the same results as the earlier example but without the need to keep track of the header scope instance. ### with_receipt A {OnStomp::Components::Scopes::ReceiptScope receipt} scope is a convenient way to use the same receipt callback for multiple receipts. You can create a new receipt scope from a client by calling {OnStomp::Components::Scopes#with\_receipt with\_receipt} with the shared callback: !!!ruby scope = client.with_receipt do |r| puts "Got my receipt!" end scope.send '/queue/test', 'walks in to the room' scope.subscribe '/queue/test2' This code sample will instruct the broker to create RECEIPT frames for client generated SEND and SUBSCRIBE frames. The receipt scope takes care of generating unique values for the `receipt` header of each frame. If you only need the receipt handler for one frame, you can use a bit of method chaining: !!!ruby client.with_receipt do |r| puts "Broker got DISCONNECT" end.disconnect ### transaction A {OnStomp::Components::Scopes::TransactionScope transaction} scope is a little more complicated than the previous scopes, but only marginally so. This scope is useful if you want to deliver some frames as part of a transaction, but don't want to be bothered with managing `transaction` headers manually. The simplest way to use a transaction scope is to hand it a block you want handled transactionally: !!!ruby client.transaction do |t| t.send '/queue/test', 'one of three' t.send '/queue/test', 'two of three' t.send '/queue/test', 'three of three' end When passed a block, the transaction scope will automatically transmit a BEGIN frame, deliver all transactional frames in the block with a matching `transaction` header and then send a `COMMIT` frame to complete the transaction. If an error is raised within the block, an ABORT frame will be sent to the broker to roll-back the transaction and the offending error will be re-raised. Transaction scopes also support being re-used, but a little more work is required on your part: trans = client.transaction trans.begin trans.send '/queue/test', 'one of three' trans.send '/queue/test', 'two of three' trans.send '/queue/test', 'three of three' trans.commit # First transaction is complete trans.begin trans.send '/queue/other', 'next transaction' trans.abort # Second transaction is rolled-back trans.begin trans.send '/queue/yet-another', 'last transaction' trans.commit When used like this, the transaction scope will automatically generate a new transaction id with each call to {OnStomp::Components::Scopes::TransactionScope#begin begin}, but you must manually begin and end the transactions. If you attempt to transmit a frame as part of a transaction that was already committed or aborted, the frame will be sent to the broker, but will not be a part of any transaction (ie: it will not have a `transaction` header.) This is also the case for frames that cannot be transacted (eg: SUBSCRIBE, UNSUBSCRIBE.) ## Events and Callbacks A key feature of the `onstomp` gem is the {OnStomp::Interfaces::ClientEvents event-driven} interface. A sufficient set of events can be bound to fully monitor what frames are being sent or received or event what frame information is ultimately delivered to the broker. There are two event hooks for every type of STOMP frame, one prefixed with `before_`, the other prefixed with `on_`. The difference between the two is when they are triggered: client.before_send do |frame, client_obj| # In here, frame is the SEND frame to deliver to the broker and # client_obj == client. All frame-based event callbacks are passed # these two parameters. puts "SEND frame will be sent, but hasn't been sent yet!" frame[:a_header] = 'a header set in an event callback' end client.on_send do |frame, client_obj| puts "SEND frame was delivered to the broker: #{frame[:a_header]}" # By now, the SEND frame has already been delivered to the broker, # so the following line does not change what the broker received, # but the change will be picked up by all other `on_send` callbacks # registered after this one. frame[:a_header] = 'a header changed in an event callback' end Internally, `onstomp` uses non-blocking IO calls to read from and write to the STOMP broker (for more details, check out {OnStomp::Connections::Base}.) When dealing with client-generated frames (eg: SEND, SUBSCRIBE, DISCONNECT), the `before_` and {OnStomp::Interfaces::ClientEvents#before\_transmitting before\_transmitting} events are triggered after a frame has been queued in the write buffer. Once the frame has actually been written to the underlying TCP/IP socket, the `after_transmitting` and `on_` events are triggered. Below is list illustrating the sequence client related frame events are triggered: 1. You call `client.send ...` and a SEND frame is created 1. The event `before_transmitting` is triggered for the SEND frame 1. The event `before_send` is triggered for the SEND frame 1. The SEND frame is added to the {OnStomp::Connections::Base connection}'s write buffer. 1. Some amount of time passes (perhaps a little, perhaps a lot depending on the IO load between you and the broker) 1. The SEND frame is serialized and fully written to the broker. 1. The event `after_transmitting` is triggered for the SEND frame 1. The event `on_send` is triggered for the SEND frame. 1. The frame delivery process is now complete! When broker generated frames (eg: MESSAGE, ERROR, RECEIPT) are received, the corresponding `before_` and `before_receiving` events are triggered, followed immediately by the triggering of the `on_` and `after_receiving` events. Below is a list illustrating the sequence broker related frame events are triggered: 1. The broker writes a MESSAGE frame to the TCP/IP socket. 1. Some amount of time passes (perhaps a little, perhaps a lot depending on the IO load between you and the broker) 1. The client fully reads and de-serializes the MESSAGE frame 1. The event `before_receiving` is triggered for the MESSAGE frame 1. The event `before_message` is triggered for the MESSAGE frame 1. The event `after_receiving` is triggered for the MESSAGE frame 1. The event `on_message` is triggered for the MESSAGE frame 1. The frame receiving process is now complete! Unlike transmitted frames, nothing special happens between `before_receiving` and `after_receiving`, these event prefixes exist to help ease order of execution issues you may have with received frames. In addition to all of the frame-related events, there are a few connection related events that are triggered by changes in the connection between you and the STOMP broker: `on_connection_established`, `on_connection_died`, `on_connection_terminated`, and `on_connection_closed`. These are mostly just wrappers around the similarly named {OnStomp::Interfaces::ConnectionEvents connection events}, with the added bonus that they can be bound before the client has created a connection (for more details on the difference between a client and a connection, see the {file:docs/UserNarrative.md#On\_Clients\_and\_Connections On Clients and Connections} subsection of the {file:docs/UserNarrative.md#Appendix Appendix}.) What follows is a brief run-down of these events: * `on_connection_establised` - triggered when a socket to the broker has been created and the CONNECT/CONNECTED frame exchange has taken place. * `on_connection_died` - triggered by STOMP 1.1 connections when the agreed upon heart-beating rate has not been met by either the broker or the client. * `on_connection_terminated` - triggered when the connection is closed unexpectedly (ie: when reading or writing to the socket raises an exception.) * `on_connection_closed` - triggered any time the socket is closed. All connection event callbacks will be invoked with two parameters: the client and the {OnStomp::Connections::Base connection}, respectively. ## Body Encodings The STOMP 1.1 protocol allows users to encode the bodies of frames and notify the broker (and other clients) of the encoding within the `content-type` header. OnStomp tries to do the right thing for you, but only if you're using Ruby 1.9+. Before I get into that, I'm going to first talk about what happens if you're using Ruby 1.8.7. Prior to version 1.9, Ruby treated strings as a collection of bytes without paying any mind to character encodings. As a result of this, if you are connected to a STOMP 1.1 broker, it will be up to you to set `content-type` header and its `charset` parameter appropriately. The good news is, if you don't specify a charset header, STOMP 1.1 dictates that the frame's body should be treated as binary data so at least the broker shouldn't choke on your SEND frames. Further, if your frame bodies contain ASCII or UTF-8 text, you can set the `content-type` header to 'text/whatever', and ignore its `charset` parameter because all frames that have a `content-type` header with a `text` type default to a UTF-8 encoding when `charset` is not specified. You should be aware that any MESSAGE frames the broker sends to you may contain bodies with various character encodings that Ruby will treat as a collection of bytes. The last thing to be aware of, is that STOMP 1.1 requires headers are UTF-8 encoded, so only use UTF-8 characters in your header names and values or incur the wrath of your STOMP broker. You've been warned! If you're using Ruby 1.9+, most of the work will be done for you, but there is one potential "gotcha." First off, the good stuff: use whatever encoding you like for your headers and your frame bodies. As long as the headers can be cleanly translated to UTF-8, `onstomp` will automatically do the work for you so the broker receives the UTF-8 encoded headers it expects. Furthermore, use whatever Ruby supported encoding you like for your SEND frames and `onstomp` will make sure `content-type` and its `charset` header get set accordingly. But wait, there's more! When the broker sends you a frame with a body using a Ruby supported encoding, you can rest easy knowing that `frame.body.encoding` will be there handling your big beautiful character encodings. And now that you're sitting there, content in the knowledge that `onstomp` does so much for you, it's time to hit you with the "gotcha." If the body of your SEND frame is meant to be treated as binary data, you'll need to make sure your string is using the ASCII-8BIT (aka: BINARY) encoding. This should be the case if you read your data from a file, but almost certainly won't be the case if you're data is a literal string inside your code. If your body string does not have a binary encoding, `onstomp` will assume that the body is plain text and will set the `charset` parameter of the `content-type` header, which you probably don't want if you really are working with binary data. ## Finishing Up After you're all done with your messaging exchange, make sure to disconnect! # This ensures that all buffered data is sent to the broker client.disconnect For more information on why it is so important to {OnStomp::Client#disconnect disconnect} your clients, please read the next section. ## Appendix ### What Really Goes Down when you `.disconnect` If there are a lot of frames being exchanged between you and a STOMP broker, you may notice that calling `client.disconnect` seems to hang. That's because it does! Calling {OnStomp::Client#disconnect disconnect} forces a client's connection to write all of the data in its write buffer to broker before going any further. By default, `onstomp` uses a separate thread to perform both reading and writing IO operations to keep data moving quickly. This approach has one significant draw-back: you could write a program that delivers a few SENDs, reads a bunch of MESSAGEs and then exits only to discover that not all of your SENDs actually got sent. That is because the main thread of your program terminated before the IO thread ever did its thing. Fortunately, I don't want you to have to worry about threaded non-blocking IO, so I made {OnStomp::Client#disconnect disconnect} special. As long as you call `disconnect` on your client before your program finishes, every frame you told your client to deliver will be written to the broker. The sometimes noticeable side-effect of this is that if there is a lot of traffic between your client and the STOMP broker, the write buffer may be pretty full when you call `disconnect`, which means it will take some time for all those frames to get flushed. In testing, I found this becomes most noticeable when the STOMP broker is sending lots and lots of frames (I was deliberately causing the broker to generate an ERROR frame for each of 5,000 frames I sent to it.) ### On Clients and Connections A single {OnStomp::Client client} implementation works with both STOMP 1.0 and STOMP 1.1 protocols (and, hopefully, with any future STOMP protocol.) This is made possible by the goodness that is composition: each {OnStomp::Client client} creates an instance of a {OnStomp::Connections::Base connection} when it is told to connect to its broker. The connections do the protocol-specific work of generating supported frames with their necessary components. At present, there are two concrete connection classes, one for {OnStomp::Connections::Stomp\_1\_0 STOMP 1.0} and one for {OnStomp::Connections::Stomp\_1\_1 STOMP 1.1}. The changes between STOMP 1.0 and STOMP 1.1 were meant to be largely backwards compatible, and so all of the common functionality for these connections is contained in the {OnStomp::Connections::Stomp\_1 Stomp\_1} module. With any luck, implementing future versions of the STOMP protocol will only require creating a new connection subclass that "does the right thing" and a bit of fiddling with the {OnStomp::Connections} module to register the protocol and possibly tweak how protocol negotiation goes down. ### The `open-uri` Angle The code to support `open-uri` style STOMP interaction is a direct port of the code used in the deprecated `stomper` gem. While I've tested it and it seems to be working just fine with `onstomp`, I'd like to review the code a bit more before I'll call it anything other than experimental. However, if you want to try it out, you can do so with `require 'onstomp/open-uri'`: require 'onstomp' # This will automatically require open-uri from Ruby's stdlib. require 'onstomp/open-uri' open('stomp://host.example.org/queue/onstomp/open-uri-test') do |c| c.send 'Hello from open-uri!' c.send 'Another pointless message coming at ya!' c.send 'big girls, you are beautiful' c.each do |m| puts "Got a message: #{m.body}" break end c.first(2) # => [ MESSAGE FRAME ('Another pointless..'), # MESSAGE FRAME ('big girls, you ...') ] end Again, for now this feature is experimental, but if you're using it and find any bugs, don't hesitate to report them in the [issue tracker](https://github.com/meadvillerb/onstomp/issues) ### Failing Over This is another experimental feature of `onstomp` that was a port of a `stomper` feature. This extension adds failover / reliability support to your communications with a STOMP broker. The same caveats of the `open-uri` extension apply here and feel free to report any bugs you find in the [issue tracker](https://github.com/meadvillerb/onstomp/issues). If you want to make use of the failover features, you can do so with `require 'onstomp/failover'`: require 'onstomp' require 'onstomp/failover' client = OnStomp::Failover::Client.new 'failover:(stomp://host1.example.org,stomp+ssl://host2.example.org)' client.subscribe '/queue/test' do |m| puts "Got a message: #{m.body}" end client.send '/queue/test', 'Hello from failover!' client.send '/queue/test', 'Another message coming from failover!' client.disconnect You can create a failover client by using a 'failover:' URI or an array of standard URIs. It is very important, however, that any 'failover:' URIs follow the above pattern. Using a URI such as `failover://stomp://host1.example.org,stomp://host2.example.org` or even `failover://(stomp://host1.example.org,stomp://host2.example.org)` will produce a parsing error at this time. In future releases I hope to make the failover URI parser a bit more robust, but for the time being only URIs of the form: failover:(uri1,uri2,uri3)?param1=value1¶m2=value2