onstomp 1.0.0 → 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
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