stomper 0.4 → 1.0.0
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/CHANGELOG +7 -0
- data/README.rdoc +63 -11
- data/lib/stomper.rb +13 -1
- data/lib/stomper/client.rb +3 -284
- data/lib/stomper/connection.rb +67 -114
- data/lib/stomper/frame_reader.rb +73 -0
- data/lib/stomper/frame_writer.rb +21 -0
- data/lib/stomper/frames.rb +24 -9
- data/lib/stomper/frames/abort.rb +2 -6
- data/lib/stomper/frames/ack.rb +2 -6
- data/lib/stomper/frames/begin.rb +2 -5
- data/lib/stomper/frames/client_frame.rb +30 -27
- data/lib/stomper/frames/commit.rb +1 -5
- data/lib/stomper/frames/connect.rb +2 -7
- data/lib/stomper/frames/connected.rb +11 -8
- data/lib/stomper/frames/disconnect.rb +1 -4
- data/lib/stomper/frames/error.rb +3 -8
- data/lib/stomper/frames/message.rb +15 -11
- data/lib/stomper/frames/receipt.rb +1 -6
- data/lib/stomper/frames/send.rb +1 -5
- data/lib/stomper/frames/server_frame.rb +13 -23
- data/lib/stomper/frames/subscribe.rb +9 -14
- data/lib/stomper/frames/unsubscribe.rb +3 -7
- data/lib/stomper/open_uri_interface.rb +41 -0
- data/lib/stomper/receipt_handlers.rb +23 -0
- data/lib/stomper/receiptor.rb +38 -0
- data/lib/stomper/sockets.rb +37 -0
- data/lib/stomper/subscriber.rb +76 -0
- data/lib/stomper/subscription.rb +14 -14
- data/lib/stomper/threaded_receiver.rb +59 -0
- data/lib/stomper/transaction.rb +13 -8
- data/lib/stomper/transactor.rb +50 -0
- data/lib/stomper/uri.rb +55 -0
- data/spec/client_spec.rb +7 -158
- data/spec/connection_spec.rb +13 -3
- data/spec/frame_reader_spec.rb +37 -0
- data/spec/frame_writer_spec.rb +27 -0
- data/spec/frames/client_frame_spec.rb +22 -98
- data/spec/frames/indirect_frame_spec.rb +45 -0
- data/spec/frames/server_frame_spec.rb +15 -16
- data/spec/open_uri_interface_spec.rb +132 -0
- data/spec/receiptor_spec.rb +35 -0
- data/spec/shared_connection_examples.rb +12 -17
- data/spec/spec_helper.rb +6 -0
- data/spec/subscriber_spec.rb +77 -0
- data/spec/subscription_spec.rb +11 -11
- data/spec/subscriptions_spec.rb +3 -6
- data/spec/threaded_receiver_spec.rb +33 -0
- data/spec/transaction_spec.rb +5 -5
- data/spec/transactor_spec.rb +46 -0
- metadata +30 -6
- data/lib/stomper/frames/headers.rb +0 -68
- data/spec/frames/headers_spec.rb +0 -54
data/CHANGELOG
CHANGED
data/README.rdoc
CHANGED
@@ -1,13 +1,62 @@
|
|
1
1
|
=README
|
2
2
|
|
3
|
-
Stomper is a library for connecting to and interacting with a message
|
4
|
-
service that supports the
|
5
|
-
Characteristics that distinguish it from other libraries, such the
|
6
|
-
{stomp gem}[http://github.com/js/stomp], are:
|
3
|
+
Stomper is a library for connecting to and interacting with a message broker
|
4
|
+
service that supports the Stomp 1.0 Protocol
|
7
5
|
|
8
|
-
|
9
|
-
|
10
|
-
|
6
|
+
@see http://stomp.github.com/stomp-specification-1-0.html Stomp 1.0 Specification
|
7
|
+
|
8
|
+
|
9
|
+
== OpenURI Example Usage
|
10
|
+
|
11
|
+
# A contrived example: Send two messages, receive them back
|
12
|
+
open("stomp://localhost/queue/testing") do |s|
|
13
|
+
s.puts "Hello World"
|
14
|
+
s.write "You may disagree, but I find this useful!"
|
15
|
+
messages = s.first(2) # => [ Message("Hello World"), Message("You may disagree...") ]
|
16
|
+
end
|
17
|
+
|
18
|
+
# Another example: connect, send a message to a destination and disconnect
|
19
|
+
open("stomp://localhost/topic/whatever") do |s|
|
20
|
+
s.put "PING!"
|
21
|
+
end
|
22
|
+
|
23
|
+
# Another example: connect, subscribe and process indefinitely
|
24
|
+
open("stomp://localhost/queue/worker") do |s|
|
25
|
+
s.each do |m|
|
26
|
+
logger.info "Received a message: #{m.body}"
|
27
|
+
# Do important work based on message
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# Final example: connect, get a single message, and disconnect
|
32
|
+
open("stomp+ssl://localhost/queue/odd_jobs") do |s|
|
33
|
+
# you can use get, gets, read or first, they're all the same at
|
34
|
+
# this time and for the foreseeable future.
|
35
|
+
incoming_message = s.get
|
36
|
+
# Do some work on incoming_message
|
37
|
+
end
|
38
|
+
|
39
|
+
This interface has been a goal of mine for this library for some time now. I
|
40
|
+
understand the value in a more typical socket object approach, and to that end
|
41
|
+
the client interface still exists (albeit there's been quite a lot of refractoring.)
|
42
|
+
However,
|
43
|
+
|
44
|
+
stomp = Stomper::Connection.new("stomp://localhost/")
|
45
|
+
stomp.subscribe("/queue/blather") do |m|
|
46
|
+
# ...
|
47
|
+
end
|
48
|
+
stomp.send("/topic/other", "Hello")
|
49
|
+
|
50
|
+
just doesn't feel very Ruby-esque to me. While the two methods for interacting with
|
51
|
+
Stomper connections appear very different, +s+ in the OpenURI examples above
|
52
|
+
really is a Stomper::Connection, but with some added extensions for handling
|
53
|
+
+put+, +get+, +each+, and so forth. One note: +read+, +get+ and +gets+ are
|
54
|
+
all aliases for +first+. Similarly, +puts+ is an alias for +put+. However,
|
55
|
+
+write+ is slightly different. The message broker I most often use the
|
56
|
+
STOMP protocol with is Apache ActiveMQ which, at the time of this writing, uses
|
57
|
+
the absence or presence of a `content-length` header to discriminate between a
|
58
|
+
JMS TextMessage and a BytesMessage. The +put+ and +puts+ methods will force
|
59
|
+
Stomper to omit that header, while +write+ will force its presence.
|
11
60
|
|
12
61
|
== Example Usage
|
13
62
|
|
@@ -46,6 +95,7 @@ Characteristics that distinguish it from other libraries, such the
|
|
46
95
|
client.close
|
47
96
|
|
48
97
|
== To-Do
|
98
|
+
* Provide other methods for handling the receiver.
|
49
99
|
* Provide the `pipe` method on Stomper::Client
|
50
100
|
* Allow SSL verification if requested (option :verify_ssl?)
|
51
101
|
* Re-evaluate how to handle a 'reliable' connection.
|
@@ -56,13 +106,15 @@ Stomper is released under the Apache License 2.0
|
|
56
106
|
|
57
107
|
== Pointless Backstory
|
58
108
|
|
59
|
-
Stomper began its life as a mere fork of the
|
60
|
-
|
109
|
+
Stomper began its life as a mere fork of the stomp gem.
|
110
|
+
However, as changes were made and desires grew, the fork began breaking
|
61
111
|
API compatibility with the original gem, and thus Stomper was conceived.
|
62
112
|
|
113
|
+
@see http://github.com/js/stomp Stomp
|
114
|
+
|
63
115
|
== Other Stuff
|
64
116
|
|
65
117
|
Primary Author:: Ian D. Eccles
|
66
118
|
Source Repository:: http://github.com/iande/stomper
|
67
|
-
Current Version:: 0.
|
68
|
-
Last Updated:: 2010-
|
119
|
+
Current Version:: 1.0.0
|
120
|
+
Last Updated:: 2010-09-25
|
data/lib/stomper.rb
CHANGED
@@ -1,15 +1,27 @@
|
|
1
|
+
require 'delegate'
|
1
2
|
require 'uri'
|
2
3
|
require 'io/wait'
|
3
4
|
require 'socket'
|
4
5
|
require 'thread'
|
5
6
|
require 'monitor'
|
6
7
|
require 'openssl'
|
8
|
+
require 'stomper/uri'
|
7
9
|
require 'stomper/frames'
|
10
|
+
require 'stomper/frame_reader'
|
11
|
+
require 'stomper/frame_writer'
|
12
|
+
require 'stomper/sockets'
|
13
|
+
require 'stomper/client'
|
14
|
+
require 'stomper/transactor'
|
15
|
+
require 'stomper/subscriber'
|
16
|
+
require 'stomper/receiptor'
|
17
|
+
require 'stomper/open_uri_interface'
|
18
|
+
require 'stomper/threaded_receiver'
|
8
19
|
require 'stomper/connection'
|
9
20
|
require 'stomper/transaction'
|
10
21
|
require 'stomper/subscription'
|
11
22
|
require 'stomper/subscriptions'
|
12
|
-
require 'stomper/
|
23
|
+
require 'stomper/receipt_handlers'
|
13
24
|
|
14
25
|
module Stomper
|
26
|
+
class MalformedFrameError < StandardError; end
|
15
27
|
end
|
data/lib/stomper/client.rb
CHANGED
@@ -1,58 +1,5 @@
|
|
1
1
|
module Stomper
|
2
|
-
|
3
|
-
# Instances of Client can be shared safely between threads, all mutating
|
4
|
-
# methods should be properly synchronized. Interactions with the stomp
|
5
|
-
# message broker through instances of Client are generally simpler than
|
6
|
-
# doing so through instances of Connection. Client instances do not require
|
7
|
-
# the use of Stomper::Frames::ClientFrame objects to transmit and receive
|
8
|
-
# information, instead relying on specific method calls to do so.
|
9
|
-
#
|
10
|
-
# === Example Usage
|
11
|
-
# client = Stomper::Client.new("stomp://localhost:61613")
|
12
|
-
# client.start
|
13
|
-
#
|
14
|
-
# client.subscribe("/queue/target1") do |msg|
|
15
|
-
# puts "Received Message: #{msg.body}"
|
16
|
-
# end
|
17
|
-
#
|
18
|
-
# client.send("/queue/target1", "this is a test")
|
19
|
-
# client.send("/queue/target1", "this persists", { :persistent => true })
|
20
|
-
#
|
21
|
-
# client.transaction do |t1|
|
22
|
-
# t1.send("/queue/target1", "this will never be seen")
|
23
|
-
# raise "Forced Exception"
|
24
|
-
# end
|
25
|
-
#
|
26
|
-
# client.unsubscribe("/queue/target1")
|
27
|
-
#
|
28
|
-
# client.stop
|
29
|
-
# client.close
|
30
|
-
#
|
31
|
-
class Client
|
32
|
-
attr_reader :connection, :subscriptions
|
33
|
-
|
34
|
-
# Creates a new Client instance that will connect to the stomp broker
|
35
|
-
# designated by the +uri+ parameter. Additionally, +options+ may be
|
36
|
-
# specified as a hash, and are passed along to the underlying connection.
|
37
|
-
# For details on the format of +uri+ and the acceptable +options+, see
|
38
|
-
# Stomper::Connection.
|
39
|
-
def initialize(uri, options={})
|
40
|
-
# At this time we only bother with the BasicConnection. We will need
|
41
|
-
# to write the ReliableConnection class to handle the particulars of reconnecting
|
42
|
-
# on a socket error.
|
43
|
-
#if options.has_key?(:max_retries) || options.delete(:reliable) { false }
|
44
|
-
#@connection = ReliableConnection.new(uri, options)
|
45
|
-
#else
|
46
|
-
@connection = Connection.new(uri, options)
|
47
|
-
#end
|
48
|
-
@subscriptions = Subscriptions.new
|
49
|
-
@send_lock = Mutex.new
|
50
|
-
@receive_lock = Mutex.new
|
51
|
-
@run_thread = nil
|
52
|
-
@receiving = false
|
53
|
-
@receiver_lock = Mutex.new
|
54
|
-
end
|
55
|
-
|
2
|
+
module Client
|
56
3
|
# Sends a string message specified by +body+ to the appropriate stomp
|
57
4
|
# broker destination given by +destination+. Additional headers for the
|
58
5
|
# message may be specified by the +headers+ hash where the key is the header
|
@@ -66,7 +13,7 @@ module Stomper
|
|
66
13
|
# client.send("/queue/some/destination", "hello world", { :persistent => true })
|
67
14
|
#
|
68
15
|
def send(destination, body, headers={})
|
69
|
-
|
16
|
+
transmit(Stomper::Frames::Send.new(destination, body, headers))
|
70
17
|
end
|
71
18
|
|
72
19
|
# Acknowledge to the stomp broker that a given message was received.
|
@@ -81,235 +28,7 @@ module Stomper
|
|
81
28
|
# client.ack("message-0001-00451-003031")
|
82
29
|
#
|
83
30
|
def ack(id_or_frame, headers={})
|
84
|
-
|
85
|
-
end
|
86
|
-
|
87
|
-
# Tells the stomp broker to commit a transaction named by the
|
88
|
-
# supplied +transaction_id+ parameter. When used in conjunction with
|
89
|
-
# +begin+, and +abort+, a means for manually handling transactional
|
90
|
-
# message passing is provided.
|
91
|
-
#
|
92
|
-
# See Also: transaction
|
93
|
-
def commit(transaction_id)
|
94
|
-
transmit_frame(Stomper::Frames::Commit.new(transaction_id))
|
95
|
-
end
|
96
|
-
|
97
|
-
# Tells the stomp broker to abort a transaction named by the
|
98
|
-
# supplied +transaction_id+ parameter. When used in conjunction with
|
99
|
-
# +begin+, and +commit+, a means for manually handling transactional
|
100
|
-
# message passing is provided.
|
101
|
-
#
|
102
|
-
# See Also: transaction
|
103
|
-
def abort(transaction_id)
|
104
|
-
transmit_frame(Stomper::Frames::Abort.new(transaction_id))
|
105
|
-
end
|
106
|
-
|
107
|
-
# Tells the stomp broker to begin a transaction named by the
|
108
|
-
# supplied +transaction_id+ parameter. When used in conjunction with
|
109
|
-
# +commit+, and +abort+, a means for manually handling transactional
|
110
|
-
# message passing is provided.
|
111
|
-
#
|
112
|
-
# See also: transaction
|
113
|
-
def begin(transaction_id)
|
114
|
-
transmit_frame(Stomper::Frames::Begin.new(transaction_id))
|
115
|
-
end
|
116
|
-
|
117
|
-
# Creates a new Stomper::Transaction object and evaluates
|
118
|
-
# the supplied +block+ within a transactional context. If
|
119
|
-
# the block executes successfully, the transaction is committed,
|
120
|
-
# otherwise it is aborted. This method is meant to provide a less
|
121
|
-
# tedious approach to transactional messaging than the +begin+,
|
122
|
-
# +abort+ and +commit+ methods.
|
123
|
-
#
|
124
|
-
# See also: begin, commit, abort, Stomper::Transaction
|
125
|
-
def transaction(transaction_id=nil, &block)
|
126
|
-
begin
|
127
|
-
Stomper::Transaction.new(self, transaction_id, &block)
|
128
|
-
rescue Stomper::TransactionAborted
|
129
|
-
nil
|
130
|
-
end
|
131
|
-
self
|
132
|
-
end
|
133
|
-
|
134
|
-
# Subscribes to the specified +destination+, passing along
|
135
|
-
# the optional +headers+ inside the subscription frame. When a message
|
136
|
-
# is received for this subscription, the supplied +block+ is
|
137
|
-
# called with the received message as its argument.
|
138
|
-
#
|
139
|
-
# Examples:
|
140
|
-
#
|
141
|
-
# client.subscribe("/queue/test") { |msg| puts "Got message: #{msg.body}" }
|
142
|
-
#
|
143
|
-
# client.subscribe("/queue/test", :ack => 'client', 'id' => 'subscription-001') do |msg|
|
144
|
-
# puts "Got message: #{msg.body}"
|
145
|
-
# end
|
146
|
-
#
|
147
|
-
# client.subscribe("/queue/test", :selector => 'cost > 5') do |msg|
|
148
|
-
# puts "Got message: #{msg.body}"
|
149
|
-
# end
|
150
|
-
#
|
151
|
-
# See also: unsubscribe, Stomper::Subscription
|
152
|
-
def subscribe(destination, headers={}, &block)
|
153
|
-
unless destination.is_a?(Subscription)
|
154
|
-
destination = Subscription.new(headers.merge(:destination => destination), &block)
|
155
|
-
end
|
156
|
-
@subscriptions << destination
|
157
|
-
transmit_frame(destination.to_subscribe)
|
158
|
-
self
|
159
|
-
end
|
160
|
-
|
161
|
-
# Unsubscribes from the specified +destination+. The +destination+
|
162
|
-
# parameter may be either a string, such as "/queue/test", or Stomper::Subscription
|
163
|
-
# object. If the optional +sub_id+ is supplied, the client will unsubscribe
|
164
|
-
# from the subscription with an id matching +sub_id+, regardless if the
|
165
|
-
# +destination+ parameter matches that of the registered subscription. For
|
166
|
-
# this reason, it is vital that subscription ids, if manually specified, be
|
167
|
-
# unique.
|
168
|
-
#
|
169
|
-
# Examples:
|
170
|
-
#
|
171
|
-
# client.unsubscribe("/queue/test")
|
172
|
-
# # unsubscribes from all "naive" subscriptions for "/queue/test"
|
173
|
-
#
|
174
|
-
# client.unsubscribe("/queue/does/not/matter", "sub-0013012031")
|
175
|
-
# # unsubscribes from all subscriptions with id of "sub-0013012031"
|
176
|
-
#
|
177
|
-
# client.unsubscribe(some_subscription)
|
178
|
-
#
|
179
|
-
# See also: subscribe, Stomper::Subscription
|
180
|
-
def unsubscribe(destination, sub_id=nil)
|
181
|
-
@subscriptions.remove(destination, sub_id).each do |unsub|
|
182
|
-
transmit_frame(unsub.to_unsubscribe)
|
183
|
-
end
|
184
|
-
self
|
185
|
-
end
|
186
|
-
|
187
|
-
# Starts the receiver for a Client instance. This method
|
188
|
-
# must be manually invoked in order to receive frames sent
|
189
|
-
# by the stomp broker. Be aware that a Client object's
|
190
|
-
# receiver runs in its own separate thread, and so may
|
191
|
-
# incur some performance penalties depending upon which
|
192
|
-
# Ruby environment this library is used with. The receiver
|
193
|
-
# thread may be stopped by calling the +stop+ instance method.
|
194
|
-
# If the receiver is set to non-blocking (default behavior), the
|
195
|
-
# receiving thread will sleep for a number of seconds specified by the
|
196
|
-
# :receive_delay option between receive calls.
|
197
|
-
#
|
198
|
-
# The +opts+ parameter is a hash of options, and can include:
|
199
|
-
#
|
200
|
-
# [:block] Sets the receiver to either blocking if true (default: false)
|
201
|
-
# [:receive_delay] Sets the delay in seconds between receive calls when the receiver is non-blocking (default: 0.2)
|
202
|
-
#
|
203
|
-
# See also: stop, receiving?
|
204
|
-
def start(opts={})
|
205
|
-
@connection.connect unless connected?
|
206
|
-
do_start = false
|
207
|
-
@receiver_lock.synchronize do
|
208
|
-
do_start = !receiving?
|
209
|
-
end
|
210
|
-
if do_start
|
211
|
-
blocking = opts.delete(:block) { false }
|
212
|
-
sleep_time = opts.delete(:receive_delay) { 0.2 }
|
213
|
-
@receiving = true
|
214
|
-
@run_thread = Thread.new(blocking) do |block|
|
215
|
-
while receiving?
|
216
|
-
receive(block)
|
217
|
-
sleep(sleep_time) unless block
|
218
|
-
end
|
219
|
-
end
|
220
|
-
end
|
221
|
-
self
|
222
|
-
end
|
223
|
-
|
224
|
-
# Stops the receiver for a Client instance. The methodology
|
225
|
-
# employed to stop the thread should be safe (it does not
|
226
|
-
# make use of Thread.kill) It is also safe to +start+ and
|
227
|
-
# +stop+ the receiver thread multiple times, doing so does not
|
228
|
-
# interrupt the connection to the stomp broker under normal
|
229
|
-
# circumstances. In the interest in proper performance, it is
|
230
|
-
# recommend that +stop+ be called when a Client instance is
|
231
|
-
# no longer needed (assuming the instance's receiver thread was
|
232
|
-
# started, of course.)
|
233
|
-
#
|
234
|
-
# See also: start, receiving?
|
235
|
-
def stop
|
236
|
-
do_stop = false
|
237
|
-
@receiver_lock.synchronize do
|
238
|
-
do_stop = receiving?
|
239
|
-
end
|
240
|
-
if do_stop
|
241
|
-
@receiving = false
|
242
|
-
@run_thread.join
|
243
|
-
@run_thread = nil
|
244
|
-
end
|
245
|
-
self
|
246
|
-
end
|
247
|
-
|
248
|
-
# Returns true if the receiver thread has been started
|
249
|
-
# by use of the +start+ command. Otherwise, returns false.
|
250
|
-
#
|
251
|
-
# See also: start, stop
|
252
|
-
def receiving?
|
253
|
-
@receiving
|
254
|
-
end
|
255
|
-
|
256
|
-
# Receives the next available frame from the stomp broker, if
|
257
|
-
# one is available. This method is regularly invoked by the
|
258
|
-
# receiver thread if it is created by the +start+ method; however,
|
259
|
-
# it may also be invoked manually if so desired, allowing one to
|
260
|
-
# by-pass the threaded implementation of receiving found in using
|
261
|
-
# +start+ and +stop+. If the received frame is an instance of
|
262
|
-
# Stomper::Frames::Message, this method will invoke any subscriptions
|
263
|
-
# that are responsible for the message.
|
264
|
-
#
|
265
|
-
# Note: this method does not block under normal operation, as such
|
266
|
-
# +nil+ may be returned if there are no frames available from the
|
267
|
-
# stomp broker.
|
268
|
-
#
|
269
|
-
# See also: Stomper::Subscription
|
270
|
-
def receive(block=false)
|
271
|
-
msg = @receive_lock.synchronize { @connection.receive(block) }
|
272
|
-
@subscriptions.perform(msg) if msg.is_a?(Stomper::Frames::Message)
|
273
|
-
msg
|
274
|
-
end
|
275
|
-
|
276
|
-
# Returns true if the client is connected, false otherwise.
|
277
|
-
def connected?
|
278
|
-
@connection.connected?
|
279
|
-
end
|
280
|
-
|
281
|
-
# Establishes a socket connection to the stomp broker and transmits
|
282
|
-
# the initial "CONNECT" frame requred per the Stomp protocol.
|
283
|
-
def connect
|
284
|
-
@connection.connect
|
285
|
-
end
|
286
|
-
|
287
|
-
# Disconnects from the stomp broker politely by first transmitting
|
288
|
-
# a Stomper::Frames::Disconnect frame to the broker.
|
289
|
-
def disconnect
|
290
|
-
@connection.disconnect
|
291
|
-
end
|
292
|
-
|
293
|
-
alias close disconnect
|
294
|
-
|
295
|
-
protected
|
296
|
-
# We need to synchronize frame tranmissions to one at a time.
|
297
|
-
# My suspicion is that write/puts socket methods are not atomic, so if a message
|
298
|
-
# is started then interrupted and a new message is attempted, it will
|
299
|
-
# result in either a broken connection or an inconsistent state of our
|
300
|
-
# system.
|
301
|
-
def transmit_frame(frame)
|
302
|
-
@send_lock.synchronize do
|
303
|
-
@connection.transmit(frame)
|
304
|
-
end
|
305
|
-
end
|
306
|
-
|
307
|
-
private
|
308
|
-
# Toying with an idea, probably a very bad one!
|
309
|
-
def each # :nodoc:
|
310
|
-
while connected?
|
311
|
-
yield receive
|
312
|
-
end
|
31
|
+
transmit(Stomper::Frames::Ack.ack_for(id_or_frame, headers))
|
313
32
|
end
|
314
33
|
end
|
315
34
|
end
|
data/lib/stomper/connection.rb
CHANGED
@@ -1,12 +1,24 @@
|
|
1
1
|
module Stomper
|
2
|
-
# A
|
3
|
-
#
|
4
|
-
#
|
5
|
-
#
|
6
|
-
#
|
2
|
+
# A connection to a Stomp message broker. This class includes the Client,
|
3
|
+
# Transactor, Subscriber, Receiptor and, in most cases, the ThreadedReceiver
|
4
|
+
# modules.
|
5
|
+
#
|
6
|
+
# @see Client
|
7
|
+
# @see Transactor
|
8
|
+
# @see Subscriber
|
9
|
+
# @see Receiptor
|
10
|
+
# @see ThreadedReceiver
|
7
11
|
class Connection
|
8
12
|
attr_reader :uri
|
9
|
-
|
13
|
+
|
14
|
+
class << self
|
15
|
+
def connect(uri, opts={})
|
16
|
+
connex = new(uri, opts)
|
17
|
+
connex.connect
|
18
|
+
connex
|
19
|
+
end
|
20
|
+
alias_method :open, :connect
|
21
|
+
end
|
10
22
|
|
11
23
|
# Creates a new connection to the Stomp broker specified by +uri+.
|
12
24
|
# The +uri+ parameter may be either a URI object, or something that can
|
@@ -22,25 +34,14 @@ module Stomper
|
|
22
34
|
# however, if SSL is not required, the schema is essentially ignored.
|
23
35
|
# The default port for the 'stomp+ssl' schema is 61612, all other schemas
|
24
36
|
# default to port 61613.
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
# [:connect_now] Immediately connect to the broker when a new instance is created (default: true)
|
29
|
-
def initialize(uri, opts = {})
|
30
|
-
connect_now = opts.delete(:connect_now) { true }
|
31
|
-
@uri = (uri.is_a?(URI) && uri) || URI.parse(uri)
|
32
|
-
@use_ssl = (@uri.scheme == "stomp+ssl")
|
33
|
-
@uri.host ||= 'localhost'
|
34
|
-
if @use_ssl
|
35
|
-
@uri.port ||= 61612
|
36
|
-
@ssl_context = OpenSSL::SSL::SSLContext.new
|
37
|
-
@ssl_context.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
38
|
-
else
|
39
|
-
@uri.port ||= 61613
|
37
|
+
def initialize(uri, opts={})
|
38
|
+
if opts.delete(:threaded_receiver) { true }
|
39
|
+
extend ::Stomper::ThreadedReceiver
|
40
40
|
end
|
41
|
-
@uri.
|
41
|
+
@uri = (uri.is_a?(URI) && uri) || URI.parse(uri)
|
42
|
+
raise ArgumentError, 'Expected URI schema to be one of stomp or stomp+ssl' unless @uri.respond_to?(:create_socket)
|
42
43
|
@connected = false
|
43
|
-
|
44
|
+
@writer = @reader = nil
|
44
45
|
end
|
45
46
|
|
46
47
|
|
@@ -50,118 +51,70 @@ module Stomper
|
|
50
51
|
#
|
51
52
|
# See also: new
|
52
53
|
def connect
|
53
|
-
|
54
|
-
|
55
|
-
stomp_socket = OpenSSL::SSL::SSLSocket.new(stomp_socket, @ssl_context)
|
56
|
-
stomp_socket.sync_close = true
|
57
|
-
stomp_socket.connect
|
58
|
-
end
|
59
|
-
@socket = stomp_socket
|
54
|
+
@connected = false
|
55
|
+
@socket = @uri.create_socket
|
60
56
|
transmit Stomper::Frames::Connect.new(@uri.user, @uri.password)
|
61
|
-
|
62
|
-
connect_frame = receive(true)
|
63
|
-
@connected = connect_frame.instance_of?(Stomper::Frames::Connected)
|
57
|
+
@connected = receive.instance_of?(Stomper::Frames::Connected)
|
64
58
|
end
|
65
59
|
|
66
60
|
# Returns true when there is an open connection
|
67
61
|
# established to the broker.
|
68
62
|
def connected?
|
69
|
-
# FIXME: @socket.eof? appears to block or otherwise "wonk out", not sure
|
70
|
-
# why yet.
|
71
|
-
#!(@socket.closed? || @socket.eof?)
|
72
63
|
@connected && @socket && !@socket.closed?
|
73
64
|
end
|
74
65
|
|
75
|
-
# Immediately closes the connection to the broker.
|
76
|
-
#
|
77
|
-
# See also: disconnect
|
78
|
-
def close
|
79
|
-
@socket.close if @socket
|
80
|
-
ensure
|
81
|
-
@connected = false
|
82
|
-
end
|
83
|
-
|
84
66
|
# Transmits a Stomper::Frames::Disconnect frame to the broker
|
85
67
|
# then terminates the connection by invoking +close+.
|
86
|
-
#
|
87
|
-
# See also: close
|
88
68
|
def disconnect
|
89
|
-
|
90
|
-
|
91
|
-
|
69
|
+
begin
|
70
|
+
@connected = false
|
71
|
+
transmit(Stomper::Frames::Disconnect.new)
|
72
|
+
ensure
|
73
|
+
close_socket
|
74
|
+
end
|
92
75
|
end
|
93
76
|
|
94
|
-
|
95
|
-
# a string conforming to the Stomp protocol and sends it
|
96
|
-
# to the broker.
|
97
|
-
def transmit(frame)
|
98
|
-
@socket.write(frame.to_stomp)
|
99
|
-
end
|
77
|
+
alias_method :close, :disconnect
|
100
78
|
|
101
|
-
#
|
102
|
-
#
|
103
|
-
# the
|
104
|
-
#
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
# this method will raise an exception. [Type the exception, don't rely on
|
113
|
-
# a basic RuntimeError or whatever the default is.]
|
114
|
-
def receive(blocking=false)
|
115
|
-
command = ''
|
116
|
-
while (ready? || blocking) && (command = @socket.gets)
|
117
|
-
command.chomp!
|
118
|
-
break if command.size > 0
|
119
|
-
end
|
120
|
-
# If we got a command, continue on, potentially blocking until
|
121
|
-
# the entire message is received, otherwise we bail out now.
|
122
|
-
return nil if command.nil? || command.size == 0
|
123
|
-
headers = {}
|
124
|
-
while (line = @socket.gets)
|
125
|
-
line.chomp!
|
126
|
-
break if line.size == 0
|
127
|
-
delim = line.index(':')
|
128
|
-
if delim
|
129
|
-
key = line[0..(delim-1)]
|
130
|
-
val = line[(delim+1)..-1]
|
131
|
-
headers[key] = val
|
132
|
-
end
|
133
|
-
end
|
134
|
-
body = nil
|
135
|
-
# Have we been given a content length?
|
136
|
-
if headers['content-length']
|
137
|
-
body = @socket.read(headers['content-length'].to_i)
|
138
|
-
raise "Invalid message terminator or content-length header" if socket_c_to_i(@socket.getc) != 0
|
139
|
-
else
|
140
|
-
body = ''
|
141
|
-
# We read until we find the first nil character
|
142
|
-
while (c = @socket.getc)
|
143
|
-
# Both Ruby 1.8 and 1.9 should support this even though the behavior
|
144
|
-
# of getc is different between the two. However, jruby is particular
|
145
|
-
# about this. And that sucks.
|
146
|
-
break if socket_c_to_i(c) == 0
|
147
|
-
body << socket_c_to_chr(c)
|
148
|
-
end
|
79
|
+
# Transmits the a Stomp Frame to the connected Stomp broker by
|
80
|
+
# way of an internal FrameWriter. If an exception is raised
|
81
|
+
# during the transmission, the connection will be forcibly closed
|
82
|
+
# and the exception will be propegated.
|
83
|
+
def transmit(frame)
|
84
|
+
begin
|
85
|
+
@socket.transmit_frame(frame)
|
86
|
+
frame
|
87
|
+
rescue Exception => ioerr
|
88
|
+
close_socket :lost_connection
|
89
|
+
raise ioerr
|
149
90
|
end
|
150
|
-
# Messages should be forever immutable.
|
151
|
-
Stomper::Frames::ServerFrame.build(command, headers, body).freeze
|
152
91
|
end
|
153
92
|
|
154
|
-
|
155
|
-
|
156
|
-
|
93
|
+
# Receives the next Stomp Frame from an internal FrameReader that
|
94
|
+
# wraps the underlying Stomp broker connection. If an exception is raised
|
95
|
+
# during the fetch, the connection will be forcibly closed and the exception
|
96
|
+
# will be propegated.
|
97
|
+
def receive()
|
98
|
+
begin
|
99
|
+
@socket.receive_frame
|
100
|
+
rescue Exception => ioerr
|
101
|
+
close_socket :lost_connection
|
102
|
+
raise ioerr
|
103
|
+
end
|
157
104
|
end
|
158
105
|
|
159
|
-
|
160
|
-
|
106
|
+
# Immediately closes the connection to the broker, without the
|
107
|
+
# formality of sending a Disconnect frame.
|
108
|
+
#
|
109
|
+
# See also: disconnect
|
110
|
+
def close_socket(conx_state = :disconnected)
|
111
|
+
@socket.shutdown(2)
|
112
|
+
@socket.close if @socket
|
161
113
|
end
|
162
114
|
|
163
|
-
|
164
|
-
|
165
|
-
|
115
|
+
include ::Stomper::Client
|
116
|
+
include ::Stomper::Transactor
|
117
|
+
include ::Stomper::Subscriber
|
118
|
+
include ::Stomper::Receiptor
|
166
119
|
end
|
167
120
|
end
|