stomper 0.4 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. data/CHANGELOG +7 -0
  2. data/README.rdoc +63 -11
  3. data/lib/stomper.rb +13 -1
  4. data/lib/stomper/client.rb +3 -284
  5. data/lib/stomper/connection.rb +67 -114
  6. data/lib/stomper/frame_reader.rb +73 -0
  7. data/lib/stomper/frame_writer.rb +21 -0
  8. data/lib/stomper/frames.rb +24 -9
  9. data/lib/stomper/frames/abort.rb +2 -6
  10. data/lib/stomper/frames/ack.rb +2 -6
  11. data/lib/stomper/frames/begin.rb +2 -5
  12. data/lib/stomper/frames/client_frame.rb +30 -27
  13. data/lib/stomper/frames/commit.rb +1 -5
  14. data/lib/stomper/frames/connect.rb +2 -7
  15. data/lib/stomper/frames/connected.rb +11 -8
  16. data/lib/stomper/frames/disconnect.rb +1 -4
  17. data/lib/stomper/frames/error.rb +3 -8
  18. data/lib/stomper/frames/message.rb +15 -11
  19. data/lib/stomper/frames/receipt.rb +1 -6
  20. data/lib/stomper/frames/send.rb +1 -5
  21. data/lib/stomper/frames/server_frame.rb +13 -23
  22. data/lib/stomper/frames/subscribe.rb +9 -14
  23. data/lib/stomper/frames/unsubscribe.rb +3 -7
  24. data/lib/stomper/open_uri_interface.rb +41 -0
  25. data/lib/stomper/receipt_handlers.rb +23 -0
  26. data/lib/stomper/receiptor.rb +38 -0
  27. data/lib/stomper/sockets.rb +37 -0
  28. data/lib/stomper/subscriber.rb +76 -0
  29. data/lib/stomper/subscription.rb +14 -14
  30. data/lib/stomper/threaded_receiver.rb +59 -0
  31. data/lib/stomper/transaction.rb +13 -8
  32. data/lib/stomper/transactor.rb +50 -0
  33. data/lib/stomper/uri.rb +55 -0
  34. data/spec/client_spec.rb +7 -158
  35. data/spec/connection_spec.rb +13 -3
  36. data/spec/frame_reader_spec.rb +37 -0
  37. data/spec/frame_writer_spec.rb +27 -0
  38. data/spec/frames/client_frame_spec.rb +22 -98
  39. data/spec/frames/indirect_frame_spec.rb +45 -0
  40. data/spec/frames/server_frame_spec.rb +15 -16
  41. data/spec/open_uri_interface_spec.rb +132 -0
  42. data/spec/receiptor_spec.rb +35 -0
  43. data/spec/shared_connection_examples.rb +12 -17
  44. data/spec/spec_helper.rb +6 -0
  45. data/spec/subscriber_spec.rb +77 -0
  46. data/spec/subscription_spec.rb +11 -11
  47. data/spec/subscriptions_spec.rb +3 -6
  48. data/spec/threaded_receiver_spec.rb +33 -0
  49. data/spec/transaction_spec.rb +5 -5
  50. data/spec/transactor_spec.rb +46 -0
  51. metadata +30 -6
  52. data/lib/stomper/frames/headers.rb +0 -68
  53. data/spec/frames/headers_spec.rb +0 -54
data/CHANGELOG CHANGED
@@ -1,6 +1,13 @@
1
+ == 1.0 2010-09-24
2
+
3
+ * Major additions to api including open-uri style interface.
4
+
5
+
1
6
  == 0.4 2010-04-23
2
7
 
3
8
  * Incremented minor version, beginning expansion.
9
+ * Need to make a list of changes I want to make.
10
+
4
11
 
5
12
  == 0.3 2010-03-04
6
13
 
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 queue
4
- service that supports the {Stomp protocol}[http://stomp.codehaus.org/Protocol].
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
- * Transaction blocks with {transaction blocks}[link:classes/Stomper/Client.html#M000066]
9
- * Playing nice with Apache ActiveMQ by being able to {disable automatic content-length generation}[link:classes/Stomper/Frames/ClientFrame.html#M000014]
10
- * A receiver thread that can be bypassed by something else! (whatever that means)
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 {Stomp}[http://github.com/js/stomp]
60
- gem. However, as changes were made and desires grew, the fork began breaking
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.3.2
68
- Last Updated:: 2010-03-05
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/client'
23
+ require 'stomper/receipt_handlers'
13
24
 
14
25
  module Stomper
26
+ class MalformedFrameError < StandardError; end
15
27
  end
@@ -1,58 +1,5 @@
1
1
  module Stomper
2
- # A high-level representation of a connection to a Stomp message broker.
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
- transmit_frame(Stomper::Frames::Send.new(destination, body, headers))
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
- transmit_frame(Stomper::Frames::Ack.ack_for(id_or_frame, headers))
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
@@ -1,12 +1,24 @@
1
1
  module Stomper
2
- # A low level connection to a Stomp message broker.
3
- # Instances of Connection are not synchronized and thus not
4
- # directly thread safe. This is a deliberate decision as instances of
5
- # Stomper::Client are the preferred way of communicating with
6
- # Stomp message broker services.
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
- attr_reader :socket
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
- # The +opts+ parameter is a hash of options, and can include:
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.freeze
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
- connect if connect_now
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
- stomp_socket = TCPSocket.open(@uri.host, @uri.port)
54
- if @use_ssl
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
- # Block until the first frame is received
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
- transmit(Stomper::Frames::Disconnect.new)
90
- ensure
91
- close
69
+ begin
70
+ @connected = false
71
+ transmit(Stomper::Frames::Disconnect.new)
72
+ ensure
73
+ close_socket
74
+ end
92
75
  end
93
76
 
94
- # Converts an instance of Stomper::Frames::ClientFrame into
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
- # Receives a single Stomper::Frames::ServerFrame from the broker.
102
- # If the frame received is known to the Stomper library, an instance of
103
- # the appropriate subclass will be returned (eg: Stomper::Frames::Message),
104
- # otherwise an instance of Stomper::Frames::ServerFrame is returned with
105
- # the +command+ attribute set to the frame type.
106
- # If the +blocking+ parameter is set to true, +receive+ will
107
- # block until there is a frame available from the server, otherwise if no frame
108
- # is currently available, +nil+ is returned.
109
- #
110
- # If an incoming message is malformed (not terminated with a NULL (\0)
111
- # character, or has an incorrectly specified +content+-+length+ header,
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
- private
155
- def ready?
156
- (@use_ssl) ? @socket.io.ready? : @socket.ready?
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
- def socket_c_to_i(c)
160
- (c.respond_to?(:ord)) ? c.ord : c
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
- def socket_c_to_chr(c)
164
- (c.respond_to?(:chr)) ? c.chr : c
165
- end
115
+ include ::Stomper::Client
116
+ include ::Stomper::Transactor
117
+ include ::Stomper::Subscriber
118
+ include ::Stomper::Receiptor
166
119
  end
167
120
  end