onstomp 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. data/.gitignore +4 -0
  2. data/.yardopts +2 -1
  3. data/Rakefile +147 -0
  4. data/extra_doc/API.md +491 -0
  5. data/extra_doc/API.md.erb +33 -0
  6. data/extra_doc/DeveloperNarrative.md +123 -0
  7. data/extra_doc/UserNarrative.md +511 -0
  8. data/lib/onstomp.rb +5 -5
  9. data/lib/onstomp/client.rb +6 -1
  10. data/lib/onstomp/components/frame.rb +6 -6
  11. data/lib/onstomp/components/frame_headers.rb +18 -18
  12. data/lib/onstomp/components/scopes/transaction_scope.rb +11 -11
  13. data/lib/onstomp/components/subscription.rb +2 -2
  14. data/lib/onstomp/components/threaded_processor.rb +1 -1
  15. data/lib/onstomp/components/uri.rb +1 -1
  16. data/lib/onstomp/connections/base.rb +5 -5
  17. data/lib/onstomp/connections/heartbeating.rb +2 -2
  18. data/lib/onstomp/connections/serializers/stomp_1.rb +6 -6
  19. data/lib/onstomp/connections/serializers/stomp_1_1.rb +2 -2
  20. data/lib/onstomp/connections/stomp_1_0.rb +1 -1
  21. data/lib/onstomp/connections/stomp_1_1.rb +1 -1
  22. data/lib/onstomp/failover.rb +4 -0
  23. data/lib/onstomp/failover/buffers.rb +1 -0
  24. data/lib/onstomp/failover/buffers/receipts.rb +101 -0
  25. data/lib/onstomp/failover/buffers/written.rb +2 -2
  26. data/lib/onstomp/failover/client.rb +15 -12
  27. data/lib/onstomp/failover/failover_configurable.rb +3 -3
  28. data/lib/onstomp/failover/pools/base.rb +1 -1
  29. data/lib/onstomp/failover/uri.rb +41 -16
  30. data/lib/onstomp/interfaces/client_configurable.rb +1 -1
  31. data/lib/onstomp/interfaces/client_events.rb +30 -11
  32. data/lib/onstomp/interfaces/connection_events.rb +5 -1
  33. data/lib/onstomp/interfaces/event_manager.rb +2 -2
  34. data/lib/onstomp/interfaces/frame_methods.rb +169 -8
  35. data/lib/onstomp/interfaces/uri_configurable.rb +3 -3
  36. data/lib/onstomp/open-uri/client_extensions.rb +4 -4
  37. data/lib/onstomp/version.rb +2 -2
  38. data/onstomp.gemspec +0 -1
  39. data/spec/onstomp/components/threaded_processor_spec.rb +21 -0
  40. data/spec/onstomp/connections/base_spec.rb +15 -0
  41. data/spec/onstomp/failover/buffers/receipts_spec.rb +189 -0
  42. data/spec/onstomp/failover/buffers/written_spec.rb +167 -1
  43. data/spec/onstomp/failover/client_spec.rb +70 -1
  44. data/spec/onstomp/failover/failover_events_spec.rb +1 -2
  45. data/spec/onstomp/failover/uri_spec.rb +37 -4
  46. data/spec/onstomp/full_stacks/failover_spec.rb +76 -25
  47. data/spec/onstomp/full_stacks/onstomp_spec.rb +52 -8
  48. data/spec/onstomp/full_stacks/onstomp_ssh_spec.rb +83 -0
  49. data/spec/onstomp/full_stacks/test_broker.rb +45 -29
  50. metadata +11 -15
  51. data/DeveloperNarrative.md +0 -15
  52. data/UserNarrative.md +0 -8
@@ -0,0 +1,33 @@
1
+ # OnStomp Generated Method APIs
2
+
3
+ <% apis.each do |api| %>
4
+ ## OnStomp <%= api[:version] %> API (<%= api[:version] %>.0.0+)
5
+
6
+ <table style="width:75%; border-collapse:collapse;">
7
+ <thead>
8
+ <tr>
9
+ <th style="text-align: left;">Method</th>
10
+ <th style="width: 50%; text-align: left;">Signature</th>
11
+ <% api[:protocols].each do |proto| %>
12
+ <th style="text-align: left;">STOMP <%= proto %></th>
13
+ <% end %>
14
+ </tr>
15
+ </thead>
16
+ <tfoot>
17
+ </tfoot>
18
+ <tbody>
19
+ <% api[:methods].each do |meth| %>
20
+ <tr>
21
+ <td>{<%= meth[:yard_link] %> <%= meth[:name]%>}</td>
22
+ <td>
23
+ <code><%= meth[:signature] %></code>
24
+ </td>
25
+ <% meth[:protocols].each do |proto| %>
26
+ <td style="background-color: #<%= proto ? 'bfb' : 'fbb' %>;"><%= proto %></td>
27
+ <% end %>
28
+ </tr>
29
+ <% end %>
30
+ </tbody>
31
+ </table>
32
+
33
+ <% end %>
@@ -0,0 +1,123 @@
1
+ # A Narrative for Developers
2
+
3
+ This document explores the `OnStomp` library through a narrative aimed at end
4
+ developers who wish to extend or modify the library. It will start with the
5
+ basics and work through the important code through exposition and examples.
6
+ It may be helpful to
7
+ review the [STOMP specification](http://stomp.github.com/index.html) before
8
+ diving into this document. It's also important to note that `onstomp` can
9
+ only be used with Ruby 1.8.7+. Support for Rubies prior to 1.8.7 does not
10
+ exist, and even requiring the library in your code will probably generate
11
+ errors.
12
+
13
+ ## Clients & Frames
14
+
15
+ An instance of {OnStomp::Client} is the primary way a user will interact
16
+ with the `OnStomp` gem. It provides the helper methods to generate STOMP
17
+ frames by including the {OnStomp::Interfaces::FrameMethods} mixin, and allows
18
+ binding event callbacks by including {OnStomp::Interfaces::ClientEvents}
19
+ (which in turn includes {OnStomp::Interfaces::EventManager}.) Every client
20
+ instance will provide the same frame methods, regardless of the version of
21
+ the STOMP protocol being used. This is accomplished through by a client's use
22
+ of {OnStomp::Connections connections} and
23
+ {OnStomp::Connections::Serializers serializers} to actually generate the
24
+ frames and convert them to their appropriate string representation. You can
25
+ read more about these components in a later section.
26
+
27
+ All of the frame methods (eg: {OnStomp::Interfaces::FrameMethods#send send},
28
+ {OnStomp::Interfaces::FrameMethods#send unsubscribe}) will either generate a
29
+ new {OnStomp::Components::Frame} instance or raise an error if the STOMP
30
+ protocol version negotiated between broker and client does not support
31
+ the requested command (eg: STOMP 1.0 does not support NACK frames.) All
32
+ frame instances are composed of a {OnStomp::Components::Frame#command command},
33
+ a set of {OnStomp::Components::Frame#headers headers}, and a
34
+ {OnStomp::Components::Frame#body body}.
35
+
36
+ A frame's {OnStomp::Components::Frame#command command} attribute is a string
37
+ that matches the corresponding STOMP command (eg: SEND, RECEIPT) with one
38
+ exception: heart-beats. The STOMP 1.1 protocol supports "heart-beating" to
39
+ let brokers and clients know that the connection is still active on the other
40
+ end by sending bare line-feed (ie: `\n`) characters between frame exchanges.
41
+ As a result, calling {OnStomp::Interfaces::FrameMethods#beat beat} on a client
42
+ will generate a frame whose command attribute is `nil`. This in turn lets the
43
+ serializer know that this isn't a true frame and it will convert it to
44
+ +"\n"+ instead of performing the normal serialization operation.
45
+
46
+ A frame's {OnStomp::Components::FrameHeaders headers} behave much like a
47
+ standard Ruby `Hash` but with a few important differences. First, the order
48
+ that header names were added is preserved. This is also true of Ruby 1.9+
49
+ hashes but not those of Ruby 1.8.7, and as a result there is some code
50
+ to maintain the ordering when running 1.8.7. Second, accessing headers
51
+ is somewhat indifferent:
52
+
53
+ frame[:some_header] = 'a value'
54
+ frame["some_header"] #=> 'a value'
55
+
56
+ What actually happens is all header names are converted to `Symbol`s
57
+ (by calling `obj.to_sym`) before any setting or getting takes place. Using
58
+ an object as a header name that does not respond to `to_sym` will raise an
59
+ error. The final major difference between headers and hashes is that header
60
+ values are only strings:
61
+
62
+ frame[:another_header] = 42
63
+ frame[:another_header] #=> '42'
64
+
65
+ If the object passed as a header value cannot be converted to a string an
66
+ error will be raised.
67
+
68
+ For most kinds of frames, the {OnStomp::Components::Frame#body body} attribute
69
+ will often be an empty string or `nil`. The only frames that support
70
+ non-empty bodies are SEND, MESSAGE, and ERROR.
71
+
72
+ ## Events
73
+
74
+ ### Frame-centric Events
75
+
76
+ Event triggering sequence for client generated frames:
77
+
78
+ 1. `client.send ...` is invoked and a SEND frame is created
79
+ 1. The event `before_transmitting` is triggered for the SEND frame
80
+ 1. The event `before_send` is triggered for the SEND frame
81
+ 1. The SEND frame is added to the {OnStomp::Connections::Base connection}'s
82
+ write buffer
83
+ 1. Some amount of time passes
84
+ 1. The SEND frame is serialized and fully written to the broker.
85
+ 1. The event `after_transmitting` is triggered for the SEND frame
86
+ 1. The event `on_send` is triggered for the SEND frame
87
+
88
+ Event triggering sequence for broker generated frames:
89
+
90
+ 1. The broker writes a MESSAGE frame to the TCP/IP socket
91
+ 1. Some amount of time passes
92
+ 1. The client fully reads and de-serializes the MESSAGE frame
93
+ 1. The event `before_receiving` is triggered for the MESSAGE frame
94
+ 1. The event `before_message` is triggered for the MESSAGE frame
95
+ 1. The event `after_receiving` is triggered for the MESSAGE frame
96
+ 1. The event `on_message` is triggered for the MESSAGE frame
97
+
98
+ ### Connection-centric Events
99
+
100
+ Event trigger sequence for connection events:
101
+
102
+ 1. An IO error occurs while the connection is reading or writing
103
+ 1. The connection closes its socket
104
+ 1. The connection triggers :on\_terminated
105
+ 1. The connection triggers :on\_closed
106
+
107
+ ## Subscription and Receipt Management
108
+
109
+ ### Subscription Manager
110
+
111
+ ### Receipt Manager
112
+
113
+ ## URI Based Configuration
114
+
115
+ ## Processors
116
+
117
+ ## Connections & Serializers
118
+
119
+ ### Connections
120
+
121
+ ### Serializers
122
+
123
+
@@ -0,0 +1,511 @@
1
+ # A Narrative for Users
2
+
3
+ This document explores the `OnStomp` API through a narrative aimed at end
4
+ users of the library. It will start with the basics and work through the
5
+ available features through exposition and examples. It may be helpful to
6
+ review the [STOMP specification](http://stomp.github.com/index.html) before
7
+ diving into this document. It's also important to note that `onstomp` can
8
+ only be used with Ruby 1.8.7+. Support for Rubies prior to 1.8.7 does not
9
+ exist, and even requiring the library in your code will probably generate
10
+ errors.
11
+
12
+ ## Creating a STOMP Client
13
+
14
+ Creating a {OnStomp::Client client} connection to a STOMP broker is done
15
+ by creating a new client and connecting it. This can be accomplished a few
16
+ different ways.
17
+
18
+ !!!ruby
19
+ require 'onstomp'
20
+
21
+ # The common way
22
+ client = OnStomp::Client.new("stomp://host.example.org")
23
+ client.connect
24
+
25
+ # A short-cut
26
+ client = OnStomp.connect "stomp://host.example.org"
27
+
28
+ The {OnStomp.connect} method creates a new client instance and immediately
29
+ calls {OnStomp::Client#connect connect} on it. This method is also aliased as
30
+ `open`, so use the verbiage you're most comfortable with.
31
+
32
+ Once connected, frames can be sent to the STOMP broker through a series of
33
+ convenient (and fairly common amongst most STOMP clients) methods such as
34
+ {OnStomp::Interfaces::FrameMethods#send send},
35
+ {OnStomp::Interfaces::FrameMethods#subscribe subscribe} and
36
+ {OnStomp::Interfaces::FrameMethods#ack ack}. A full list of the frame methods can
37
+ be found in the documentation for the {OnStomp::Interfaces::FrameMethods}
38
+ mixin.
39
+
40
+ So, let's send some SEND frame to the broker:
41
+
42
+ !!!ruby
43
+ client.send '/queue/test', 'Hello World!'
44
+ client.send '/queue/test', 'Persist this, please.', :persistent => true
45
+
46
+ Most frame-generating methods treat the last parameter as a hash of headers
47
+ to include with the generated frame. The only exception to this is the
48
+ {OnStomp::Interfaces::FrameMethods#beat heart-beat} frame, which has no
49
+ command, headers or body.
50
+
51
+ ## Subscriptions and Receipts
52
+
53
+ ### Subscribing: Send me stuff, and maybe I'll tell you when I got it.
54
+
55
+ Subscriptions in `onstomp` are pretty much just blocks that get called every
56
+ time a MESSAGE frame is received that matches a previously sent SUBSCRIBE frame.
57
+
58
+ To set up a subscription, just pass a block to the
59
+ {OnStomp::Interfaces::FrameMethods#subscribe subscribe} method:
60
+
61
+ !!!ruby
62
+ client.subscribe '/queue/test' do |msg|
63
+ # Invoked every time the broker delivers a MESSAGE frame for the
64
+ # SUBSCRIBE frame generated by this method call.
65
+ puts "Got a message: #{msg.body}"
66
+ end
67
+
68
+ The STOMP protocol supports a few different ways of acknowledging that MESSAGE
69
+ frames were received, depending upon the protocol version. STOMP 1.0
70
+ connections support automatic acknowledgment (the default behavior) and
71
+ client-side message acknowledgment. STOMP 1.1 adds a `client-individual` mode
72
+ that may behave differently depending upon the broker you are using. It is
73
+ considered incorrect for a client to acknowledge MESSAGE frames with ACK
74
+ frames if the subscription is operating in `auto` mode. To set the ack mode
75
+ of a subscription, include an `:ack` header in your call to
76
+ {OnStomp::Interfaces::FrameMethods#subscribe subscribe}:
77
+
78
+ !!!ruby
79
+ # Technically, this isn't needed as auto is the default ack mode
80
+ client.subscribe '/queue/test', :ack => 'auto' do |msg|
81
+ # process the MESSAGE frame
82
+ # ...
83
+ end
84
+
85
+ # Set the subscription's ack mode to client
86
+ client.subscribe '/queue/test', :ack => 'client' do |msg|
87
+ # process the MESSAGE frame
88
+ # ...
89
+ # Tell the broker that the MESSAGE frame was processed
90
+ client.ack msg
91
+ end
92
+
93
+ # Set the subscription's ack mode to client-individual
94
+ client.subscribe '/queue/test', :ack => 'client-individual' do |msg|
95
+ # process the MESSAGE frame
96
+ # ...
97
+ # Tell the broker that the MESSAGE frame was NOT processed
98
+ client.nack msg
99
+ end
100
+
101
+ The difference between `:ack => 'client'` and `:ack => 'client-individual`
102
+ largely depends upon the STOMP broker. Apache's [ActiveMQ](http://activemq.apache.org/)
103
+ treats ACK frames received for a MESSAGE frame as "cumulative acknowledgements,"
104
+ that is an ACK frame acknowledges the MESSAGE it was sent for and all previous
105
+ MESSAGE frames sent from the broker to the client. The STOMP 1.1 spec
106
+ clarified the expected behavior brokers should exhibit when receiving an ACK
107
+ frame, and a `client-individual` ack mode specifies that each MESSAGE frame
108
+ will be acknowledged with its own ACK (or NACK) frame. There may be brokers
109
+ that behave this way when using a `client` ack mode, so what happens when
110
+ you ACK a MESSAGE in `client` mode depends heavily on the broker being used.
111
+
112
+ The NACK frame was introduced in the STOMP 1.1 spec and gives the client a
113
+ way to tell the broker that it did not successfully process a MESSAGE. It is
114
+ very similar in structure to an ACK frame, which makes sense it is little
115
+ more than a "Not ACK".
116
+
117
+ ### Receipts: Did you get that thing I sent you?
118
+
119
+ Most frames a client sends to a STOMP broker can be receipted (ie: the
120
+ client can instruct the broker to send a RECEIPT frame after it receives
121
+ the original frame.) The two exceptions to this are heart-beat frames (as
122
+ mentioned previously, heart-beats really aren't frames) and CONNECT
123
+ frames. The client does this by including a `receipt` header that specifies
124
+ an receipt ID for the frame being sent, the broker will in turn deliver a
125
+ RECEIPT frame with a matching `receipt-id` header. In OnStomp, requesting a
126
+ receipt for a SEND frame is as easy as including a block with your call to
127
+ {OnStomp::Interfaces::FrameMethods#send send}:
128
+
129
+ !!!ruby
130
+ client.send '/queue/test', 'Did you get this?' do |r|
131
+ puts "Got my receipt: #{r[:'receipt-id']}"
132
+ end
133
+
134
+ To request receipts for other types of frames, see
135
+ {file:docs/UserNarrative.md#with\_receipt with\_receipt} subsection of
136
+ {file:docs/UserNarrative.md#Scopes Scopes}.
137
+
138
+ ## Scopes
139
+
140
+ Sometimes you want to do the same stuff with a series of frames, and that's
141
+ why we have {OnStomp::Components::Scopes scopes}.
142
+
143
+ ### with_headers
144
+
145
+ A {OnStomp::Components::Scopes::HeaderScope header} scope is a convenient way to
146
+ apply a common set of headers to a series of frames. You can create a new
147
+ header scope from a client by calling
148
+ {OnStomp::Components::Scopes#with\_headers with\_headers}:
149
+
150
+ !!!ruby
151
+ scope = client.with_headers :persistent => true, :'content-type' => 'text/plain'
152
+ scope.send '/queue/test', 'walks in to the room'
153
+ scope.send '/queue/test', 'feels like a big balloon', :persitent => false
154
+ scope.send '/queue/test', 'big girls, you are beautiful'
155
+
156
+ All of the SEND frames generated will have a `content-type` header with a value
157
+ of `text/plain`. The first and last frames will also have a header of
158
+ `persistent` with a value of `true`; however, the middle frame's `persistent`
159
+ header will have a value of `false`. As illustrated, headers specified on
160
+ the frame-generating methods will override those defined for the scope.
161
+
162
+ If you want to apply the headers to a series of frames in one swoop and don't
163
+ need to keep a `scope` variable around, you can pass a block to `with_headers`:
164
+
165
+ !!!ruby
166
+ client.with_headers :persistent => true, :'content-type' => 'text/plain' do |h|
167
+ h.send '/queue/test', 'walks in to the room'
168
+ h.send '/queue/test', 'feels like a big balloon', :persitent => false
169
+ h.send '/queue/test', 'big girls, you are beautiful'
170
+ end
171
+
172
+ This code sample produces the same results as the earlier example but without
173
+ the need to keep track of the header scope instance.
174
+
175
+ ### with_receipt
176
+
177
+ A {OnStomp::Components::Scopes::ReceiptScope receipt} scope is a convenient way
178
+ to use the same receipt callback for multiple receipts. You can create a new
179
+ receipt scope from a client by calling
180
+ {OnStomp::Components::Scopes#with\_receipt with\_receipt} with the shared
181
+ callback:
182
+
183
+ !!!ruby
184
+ scope = client.with_receipt do |r|
185
+ puts "Got my receipt!"
186
+ end
187
+
188
+ scope.send '/queue/test', 'walks in to the room'
189
+ scope.subscribe '/queue/test2'
190
+
191
+ This code sample will instruct the broker to create RECEIPT frames for client
192
+ generated SEND and SUBSCRIBE frames. The receipt scope takes care of generating
193
+ unique values for the `receipt` header of each frame. If you only need the
194
+ receipt handler for one frame, you can use a bit of method chaining:
195
+
196
+ !!!ruby
197
+ client.with_receipt do |r|
198
+ puts "Broker got DISCONNECT"
199
+ end.disconnect
200
+
201
+ ### transaction
202
+
203
+ A {OnStomp::Components::Scopes::TransactionScope transaction} scope is a little
204
+ more complicated than the previous scopes, but only marginally so. This scope
205
+ is useful if you want to deliver some frames as part of a transaction, but
206
+ don't want to be bothered with managing `transaction` headers manually.
207
+ The simplest way to use a transaction scope is to hand it a block you want
208
+ handled transactionally:
209
+
210
+ !!!ruby
211
+ client.transaction do |t|
212
+ t.send '/queue/test', 'one of three'
213
+ t.send '/queue/test', 'two of three'
214
+ t.send '/queue/test', 'three of three'
215
+ end
216
+
217
+ When passed a block, the transaction scope will automatically transmit a
218
+ BEGIN frame, deliver all transactional frames in the block with a matching
219
+ `transaction` header and then send a `COMMIT` frame to complete the
220
+ transaction. If an error is raised within the block, an ABORT frame will be
221
+ sent to the broker to roll-back the transaction and the offending error
222
+ will be re-raised.
223
+
224
+ Transaction scopes also support being re-used, but a little more work is
225
+ required on your part:
226
+
227
+ trans = client.transaction
228
+ trans.begin
229
+ trans.send '/queue/test', 'one of three'
230
+ trans.send '/queue/test', 'two of three'
231
+ trans.send '/queue/test', 'three of three'
232
+ trans.commit
233
+ # First transaction is complete
234
+ trans.begin
235
+ trans.send '/queue/other', 'next transaction'
236
+ trans.abort
237
+ # Second transaction is rolled-back
238
+ trans.begin
239
+ trans.send '/queue/yet-another', 'last transaction'
240
+ trans.commit
241
+
242
+ When used like this, the transaction scope will automatically generate a new
243
+ transaction id with each call to
244
+ {OnStomp::Components::Scopes::TransactionScope#begin begin}, but you must
245
+ manually begin and end the transactions. If you attempt to transmit a frame
246
+ as part of a transaction that was already committed or aborted, the frame
247
+ will be sent to the broker, but will not be a part of any transaction (ie:
248
+ it will not have a `transaction` header.) This is also the case for frames
249
+ that cannot be transacted (eg: SUBSCRIBE, UNSUBSCRIBE.)
250
+
251
+ ## Events and Callbacks
252
+
253
+ A key feature of the `onstomp` gem is the
254
+ {OnStomp::Interfaces::ClientEvents event-driven} interface. A sufficient set
255
+ of events can be bound to fully monitor what frames are being sent or received
256
+ or event what frame information is ultimately delivered to the broker. There
257
+ are two event hooks for every type of STOMP frame, one prefixed with
258
+ `before_`, the other prefixed with `on_`. The difference between the two
259
+ is when they are triggered:
260
+
261
+ client.before_send do |frame, client_obj|
262
+ # In here, frame is the SEND frame to deliver to the broker and
263
+ # client_obj == client. All frame-based event callbacks are passed
264
+ # these two parameters.
265
+ puts "SEND frame will be sent, but hasn't been sent yet!"
266
+ frame[:a_header] = 'a header set in an event callback'
267
+ end
268
+
269
+ client.on_send do |frame, client_obj|
270
+ puts "SEND frame was delivered to the broker: #{frame[:a_header]}"
271
+ # By now, the SEND frame has already been delivered to the broker,
272
+ # so the following line does not change what the broker received,
273
+ # but the change will be picked up by all other `on_send` callbacks
274
+ # registered after this one.
275
+ frame[:a_header] = 'a header changed in an event callback'
276
+ end
277
+
278
+ Internally, `onstomp` uses non-blocking IO calls to read from and write to
279
+ the STOMP broker (for more details, check out {OnStomp::Connections::Base}.)
280
+ When dealing with client-generated frames (eg: SEND, SUBSCRIBE, DISCONNECT),
281
+ the `before_<frame>` and
282
+ {OnStomp::Interfaces::ClientEvents#before\_transmitting before\_transmitting}
283
+ events are triggered after a frame has been queued in the write buffer. Once
284
+ the frame has actually been written to the underlying TCP/IP socket, the
285
+ `after_transmitting` and `on_<frame>` events are triggered.
286
+ Below is list illustrating the sequence client related frame events are
287
+ triggered:
288
+
289
+ 1. You call `client.send ...` and a SEND frame is created
290
+ 1. The event `before_transmitting` is triggered for the SEND frame
291
+ 1. The event `before_send` is triggered for the SEND frame
292
+ 1. The SEND frame is added to the {OnStomp::Connections::Base connection}'s
293
+ write buffer.
294
+ 1. Some amount of time passes (perhaps a little, perhaps a lot depending on
295
+ the IO load between you and the broker)
296
+ 1. The SEND frame is serialized and fully written to the broker.
297
+ 1. The event `after_transmitting` is triggered for the SEND frame
298
+ 1. The event `on_send` is triggered for the SEND frame.
299
+ 1. The frame delivery process is now complete!
300
+
301
+ When broker generated frames (eg: MESSAGE, ERROR, RECEIPT) are received,
302
+ the corresponding `before_<frame>` and `before_receiving` events are
303
+ triggered, followed immediately by the triggering of the `on_<frame>` and
304
+ `after_receiving` events.
305
+ Below is a list illustrating the sequence broker related frame events are
306
+ triggered:
307
+
308
+ 1. The broker writes a MESSAGE frame to the TCP/IP socket.
309
+ 1. Some amount of time passes (perhaps a little, perhaps a lot depending on
310
+ the IO load between you and the broker)
311
+ 1. The client fully reads and de-serializes the MESSAGE frame
312
+ 1. The event `before_receiving` is triggered for the MESSAGE frame
313
+ 1. The event `before_message` is triggered for the MESSAGE frame
314
+ 1. The event `after_receiving` is triggered for the MESSAGE frame
315
+ 1. The event `on_message` is triggered for the MESSAGE frame
316
+ 1. The frame receiving process is now complete!
317
+
318
+ Unlike transmitted frames, nothing special happens between `before_receiving`
319
+ and `after_receiving`, these event prefixes exist to help ease order of
320
+ execution issues you may have with received frames.
321
+
322
+ In addition to all of the frame-related events, there are a few connection
323
+ related events that are triggered by changes in the connection between
324
+ you and the STOMP broker: `on_connection_established`, `on_connection_died`,
325
+ `on_connection_terminated`, and `on_connection_closed`. These are mostly just
326
+ wrappers around the similarly named
327
+ {OnStomp::Interfaces::ConnectionEvents connection events}, with the added
328
+ bonus that they can be bound before the client has created a connection (for
329
+ more details on the difference between a client and a connection, see
330
+ the {file:docs/UserNarrative.md#On\_Clients\_and\_Connections On Clients and Connections}
331
+ subsection of the {file:docs/UserNarrative.md#Appendix Appendix}.) What follows
332
+ is a brief run-down of these events:
333
+
334
+ * `on_connection_establised` - triggered when a socket to the broker has
335
+ been created and the CONNECT/CONNECTED frame exchange has taken place.
336
+ * `on_connection_died` - triggered by STOMP 1.1 connections when the agreed
337
+ upon heart-beating rate has not been met by either the broker or the client.
338
+ * `on_connection_terminated` - triggered when the connection is closed
339
+ unexpectedly (ie: when reading or writing to the socket raises an exception.)
340
+ * `on_connection_closed` - triggered any time the socket is closed.
341
+
342
+ All connection event callbacks will be invoked with two parameters: the
343
+ client and the {OnStomp::Connections::Base connection}, respectively.
344
+
345
+ ## Body Encodings
346
+
347
+ The STOMP 1.1 protocol allows users to encode the bodies of frames and notify
348
+ the broker (and other clients) of the encoding within the `content-type`
349
+ header. OnStomp tries to do the right thing for you, but only if you're using
350
+ Ruby 1.9+. Before I get into that, I'm going to first talk about what
351
+ happens if you're using Ruby 1.8.7.
352
+
353
+ Prior to version 1.9, Ruby treated strings as a collection of bytes without
354
+ paying any mind to character encodings. As a result of this, if you are
355
+ connected to a STOMP 1.1 broker, it will be up to you to set `content-type`
356
+ header and its `charset` parameter appropriately. The good news is, if you
357
+ don't specify a charset header, STOMP 1.1 dictates that the frame's body
358
+ should be treated as binary data so at least the broker shouldn't choke
359
+ on your SEND frames. Further, if your frame bodies contain ASCII or UTF-8
360
+ text, you can set the `content-type` header to 'text/whatever', and ignore
361
+ its `charset` parameter because all frames that have a `content-type` header
362
+ with a `text` type default to a UTF-8 encoding when `charset` is not specified.
363
+ You should be aware that any MESSAGE frames the broker sends to you
364
+ may contain bodies with various character encodings that Ruby will treat as
365
+ a collection of bytes. The last thing to be aware of, is that STOMP 1.1
366
+ requires headers are UTF-8 encoded, so only use UTF-8 characters in your header
367
+ names and values or incur the wrath of your STOMP broker. You've been warned!
368
+
369
+ If you're using Ruby 1.9+, most of the work will be done for you, but there is
370
+ one potential "gotcha." First off, the good stuff: use whatever encoding
371
+ you like for your headers and your frame bodies. As long as the headers
372
+ can be cleanly translated to UTF-8, `onstomp` will automatically do the work
373
+ for you so the broker receives the UTF-8 encoded headers it expects.
374
+ Furthermore, use whatever Ruby supported encoding you like for your SEND
375
+ frames and `onstomp` will make sure `content-type` and its `charset` header
376
+ get set accordingly. But wait, there's more! When the broker sends you a frame
377
+ with a body using a Ruby supported encoding, you can rest easy knowing that
378
+ `frame.body.encoding` will be there handling your big beautiful character
379
+ encodings. And now that you're sitting there, content in the knowledge that
380
+ `onstomp` does so much for you, it's time to hit you with the "gotcha." If
381
+ the body of your SEND frame is meant to be treated as binary data, you'll
382
+ need to make sure your string is using the ASCII-8BIT (aka: BINARY) encoding.
383
+ This should be the case if you read your data from a file, but almost
384
+ certainly won't be the case if you're data is a literal string inside your
385
+ code. If your body string does not have a binary encoding, `onstomp` will
386
+ assume that the body is plain text and will set the `charset` parameter
387
+ of the `content-type` header, which you probably don't want if you really
388
+ are working with binary data.
389
+
390
+ ## Finishing Up
391
+
392
+ After you're all done with your messaging exchange, make sure to disconnect!
393
+
394
+ # This ensures that all buffered data is sent to the broker
395
+ client.disconnect
396
+
397
+ For more information on why it is so important to
398
+ {OnStomp::Client#disconnect disconnect} your clients, please read the next
399
+ section.
400
+
401
+ ## Appendix
402
+
403
+ ### What Really Goes Down when you `.disconnect`
404
+
405
+ If there are a lot of frames being exchanged between you and a STOMP broker,
406
+ you may notice that calling `client.disconnect` seems to hang. That's because
407
+ it does! Calling {OnStomp::Client#disconnect disconnect} forces a client's
408
+ connection to write all of the data in its write buffer to broker before
409
+ going any further. By default, `onstomp` uses a separate thread to perform
410
+ both reading and writing IO operations to keep data moving quickly. This
411
+ approach has one significant draw-back: you could write a program that
412
+ delivers a few SENDs, reads a bunch of MESSAGEs and then exits only to
413
+ discover that not all of your SENDs actually got sent. That is because the
414
+ main thread of your program terminated before the IO thread ever did its
415
+ thing. Fortunately, I don't want you to have to worry about threaded
416
+ non-blocking IO, so I made {OnStomp::Client#disconnect disconnect} special.
417
+ As long as you call `disconnect` on your client before your program finishes,
418
+ every frame you told your client to deliver will be written to the broker.
419
+
420
+ The sometimes noticeable side-effect of this is that if there is a lot of
421
+ traffic between your client and the STOMP broker, the write buffer may
422
+ be pretty full when you call `disconnect`, which means it will take some
423
+ time for all those frames to get flushed. In testing, I found this becomes
424
+ most noticeable when the STOMP broker is sending lots and lots of frames
425
+ (I was deliberately causing the broker to generate an ERROR frame for each
426
+ of 5,000 frames I sent to it.)
427
+
428
+ ### On Clients and Connections
429
+
430
+ A single {OnStomp::Client client} implementation works with both
431
+ STOMP 1.0 and STOMP 1.1 protocols (and, hopefully, with any future STOMP
432
+ protocol.) This is made possible by the goodness that is composition:
433
+ each {OnStomp::Client client} creates an instance of a
434
+ {OnStomp::Connections::Base connection} when it is told to connect to
435
+ its broker. The connections do the protocol-specific work of generating
436
+ supported frames with their necessary components. At present, there are
437
+ two concrete connection classes, one for
438
+ {OnStomp::Connections::Stomp\_1\_0 STOMP 1.0} and one for
439
+ {OnStomp::Connections::Stomp\_1\_1 STOMP 1.1}. The changes between STOMP 1.0
440
+ and STOMP 1.1 were meant to be largely backwards compatible, and so all of
441
+ the common functionality for these connections is contained in the
442
+ {OnStomp::Connections::Stomp\_1 Stomp\_1} module.
443
+
444
+ With any luck, implementing future versions of the STOMP protocol will only
445
+ require creating a new connection subclass that "does the right thing" and
446
+ a bit of fiddling with the {OnStomp::Connections} module to register the
447
+ protocol and possibly tweak how protocol negotiation goes down.
448
+
449
+
450
+ ### The `open-uri` Angle
451
+
452
+ The code to support `open-uri` style STOMP interaction is a direct port of
453
+ the code used in the deprecated `stomper` gem. While I've tested it and it seems
454
+ to be working just fine with `onstomp`, I'd like to review the code a bit more
455
+ before I'll call it anything other than experimental. However, if you want
456
+ to try it out, you can do so with `require 'onstomp/open-uri'`:
457
+
458
+ require 'onstomp'
459
+ # This will automatically require open-uri from Ruby's stdlib.
460
+ require 'onstomp/open-uri'
461
+
462
+ open('stomp://host.example.org/queue/onstomp/open-uri-test') do |c|
463
+ c.send 'Hello from open-uri!'
464
+ c.send 'Another pointless message coming at ya!'
465
+ c.send 'big girls, you are beautiful'
466
+
467
+ c.each do |m|
468
+ puts "Got a message: #{m.body}"
469
+ break
470
+ end
471
+
472
+ c.first(2) # => [ MESSAGE FRAME ('Another pointless..'),
473
+ # MESSAGE FRAME ('big girls, you ...') ]
474
+ end
475
+
476
+ Again, for now this feature is experimental, but if you're using it and find
477
+ any bugs, don't hesitate to report them in the
478
+ [issue tracker](https://github.com/meadvillerb/onstomp/issues)
479
+
480
+ ### Failing Over
481
+
482
+ This is another experimental feature of `onstomp` that was a port of a
483
+ `stomper` feature. This extension adds failover / reliability support to
484
+ your communications with a STOMP broker. The same caveats of the `open-uri`
485
+ extension apply here and feel free to report any bugs you find in the
486
+ [issue tracker](https://github.com/meadvillerb/onstomp/issues). If you want
487
+ to make use of the failover features, you can do so with
488
+ `require 'onstomp/failover'`:
489
+
490
+ require 'onstomp'
491
+ require 'onstomp/failover'
492
+
493
+ client = OnStomp::Failover::Client.new 'failover:(stomp://host1.example.org,stomp+ssl://host2.example.org)'
494
+
495
+ client.subscribe '/queue/test' do |m|
496
+ puts "Got a message: #{m.body}"
497
+ end
498
+ client.send '/queue/test', 'Hello from failover!'
499
+ client.send '/queue/test', 'Another message coming from failover!'
500
+
501
+ client.disconnect
502
+
503
+ You can create a failover client by using a 'failover:' URI or an array of
504
+ standard URIs. It is very important, however, that any 'failover:' URIs follow
505
+ the above pattern. Using a URI such as
506
+ `failover://stomp://host1.example.org,stomp://host2.example.org` or even
507
+ `failover://(stomp://host1.example.org,stomp://host2.example.org)` will produce
508
+ a parsing error at this time. In future releases I hope to make the failover
509
+ URI parser a bit more robust, but for the time being only URIs of the form:
510
+
511
+ failover:(uri1,uri2,uri3)?param1=value1&param2=value2