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