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