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.
- data/.gitignore +4 -0
- data/.yardopts +2 -1
- data/Rakefile +147 -0
- data/extra_doc/API.md +491 -0
- data/extra_doc/API.md.erb +33 -0
- data/extra_doc/DeveloperNarrative.md +123 -0
- data/extra_doc/UserNarrative.md +511 -0
- data/lib/onstomp.rb +5 -5
- data/lib/onstomp/client.rb +6 -1
- data/lib/onstomp/components/frame.rb +6 -6
- data/lib/onstomp/components/frame_headers.rb +18 -18
- data/lib/onstomp/components/scopes/transaction_scope.rb +11 -11
- data/lib/onstomp/components/subscription.rb +2 -2
- data/lib/onstomp/components/threaded_processor.rb +1 -1
- data/lib/onstomp/components/uri.rb +1 -1
- data/lib/onstomp/connections/base.rb +5 -5
- data/lib/onstomp/connections/heartbeating.rb +2 -2
- data/lib/onstomp/connections/serializers/stomp_1.rb +6 -6
- data/lib/onstomp/connections/serializers/stomp_1_1.rb +2 -2
- data/lib/onstomp/connections/stomp_1_0.rb +1 -1
- data/lib/onstomp/connections/stomp_1_1.rb +1 -1
- data/lib/onstomp/failover.rb +4 -0
- data/lib/onstomp/failover/buffers.rb +1 -0
- data/lib/onstomp/failover/buffers/receipts.rb +101 -0
- data/lib/onstomp/failover/buffers/written.rb +2 -2
- data/lib/onstomp/failover/client.rb +15 -12
- data/lib/onstomp/failover/failover_configurable.rb +3 -3
- data/lib/onstomp/failover/pools/base.rb +1 -1
- data/lib/onstomp/failover/uri.rb +41 -16
- data/lib/onstomp/interfaces/client_configurable.rb +1 -1
- data/lib/onstomp/interfaces/client_events.rb +30 -11
- data/lib/onstomp/interfaces/connection_events.rb +5 -1
- data/lib/onstomp/interfaces/event_manager.rb +2 -2
- data/lib/onstomp/interfaces/frame_methods.rb +169 -8
- data/lib/onstomp/interfaces/uri_configurable.rb +3 -3
- data/lib/onstomp/open-uri/client_extensions.rb +4 -4
- data/lib/onstomp/version.rb +2 -2
- data/onstomp.gemspec +0 -1
- data/spec/onstomp/components/threaded_processor_spec.rb +21 -0
- data/spec/onstomp/connections/base_spec.rb +15 -0
- data/spec/onstomp/failover/buffers/receipts_spec.rb +189 -0
- data/spec/onstomp/failover/buffers/written_spec.rb +167 -1
- data/spec/onstomp/failover/client_spec.rb +70 -1
- data/spec/onstomp/failover/failover_events_spec.rb +1 -2
- data/spec/onstomp/failover/uri_spec.rb +37 -4
- data/spec/onstomp/full_stacks/failover_spec.rb +76 -25
- data/spec/onstomp/full_stacks/onstomp_spec.rb +52 -8
- data/spec/onstomp/full_stacks/onstomp_ssh_spec.rb +83 -0
- data/spec/onstomp/full_stacks/test_broker.rb +45 -29
- metadata +11 -15
- data/DeveloperNarrative.md +0 -15
- 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¶m2=value2
|