stomper 2.0.1 → 2.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.md ADDED
@@ -0,0 +1,28 @@
1
+ # Changes
2
+
3
+ ## 2.0.2 - 2011-03-01
4
+
5
+ * updated to-do's and version number in README.md
6
+
7
+ * added CHANGELOG.md
8
+
9
+ * connection's @close@ method now clears remaining subscriptions and receipt
10
+ handlers after all events are fired. should ease the work
11
+ the failover extension has to do to re-subscribe appropriately.
12
+
13
+ * corrected spec tests to handle features introduced in 2.0.1 - should not
14
+ have pushed a new version until those specs were run.
15
+
16
+ ## 2.0.1 - 2011-02-27
17
+
18
+ * connection now raises exception in threaded receiver to stop, prevents
19
+ blocking on @receive@
20
+
21
+ * connection terminated event now fires properly, fixes #1
22
+
23
+ * subscription manager clears remaining subscriptions when the connection
24
+ is closed, fixes #2
25
+
26
+ ## 2.0.0 - 2011-02-22
27
+
28
+ * Rewrite of stomper
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- #Stomper 2.0.0
1
+ #Stomper 2.0
2
2
 
3
3
  Stomper is a library for connecting to and interacting with a message broker
4
4
  service that supports the Stomp [1.0](http://stomp.github.com/stomp-specification-1-0.html)
@@ -15,22 +15,6 @@ rather unpleasant to deal with for common use cases.
15
15
  This gem is in no way related to the [Python stomper](http://code.google.com/p/stomper/)
16
16
  library.
17
17
 
18
- ##Still in the Works
19
-
20
- There are a few features that have not yet been implemented, hence why we're
21
- still on a prerelease version number. The following is a list of the major
22
- deficiencies of the gem, but this list is not necessarily exhaustive.
23
-
24
- * Write some features to test other connections:
25
- * Authenticated logins
26
- * SSL Client verification (just to ensure that SSL params are being
27
- delivered appropriately)
28
- * A bit of refactoring is in order
29
- * Refactor specs a bit, clean up separation between 1.9 and 1.8 specific tests
30
-
31
- As these issues are resolved, I'll drop them from the list (and potentially
32
- add other issues)
33
-
34
18
  ##Example Usage
35
19
 
36
20
  # Establish a connection to the broker
@@ -5,17 +5,17 @@ Feature: Protocol version negotiation
5
5
 
6
6
  Scenario: By default, allow 1.1 from broker
7
7
  Given a Stomp 1.1 broker
8
- When a connection is established
8
+ When the connection is told to connect
9
9
  Then the connection should be using the 1.1 protocol
10
10
 
11
11
  Scenario: By default, allow 1.0 from broker
12
12
  Given a Stomp 1.0 broker
13
- When a connection is established
13
+ When the connection is told to connect
14
14
  Then the connection should be using the 1.0 protocol
15
15
 
16
16
  Scenario: By default, assume 1.0 from version-less broker
17
17
  Given an unversioned Stomp broker
18
- When a connection is established
18
+ When the connection is told to connect
19
19
  Then the connection should be using the 1.0 protocol
20
20
 
21
21
  Scenario: By default, raise error if the broker's version isn't supported
@@ -26,13 +26,13 @@ Feature: Protocol version negotiation
26
26
  Scenario: A 1.0 client should accept a 1.0 broker
27
27
  Given a Stomp 1.0 broker
28
28
  When the client protocol version is "1.0"
29
- And a connection is established
29
+ And the connection is told to connect
30
30
  Then the connection should be using the 1.0 protocol
31
31
 
32
32
  Scenario: A 1.0 client should accept a version-less broker
33
33
  Given an unversioned Stomp broker
34
34
  When the client protocol version is "1.0"
35
- And a connection is established
35
+ And the connection is told to connect
36
36
  Then the connection should be using the 1.0 protocol
37
37
 
38
38
  Scenario: A 1.0 client should not accept a 1.1 broker
@@ -44,7 +44,7 @@ Feature: Protocol version negotiation
44
44
  Scenario: A 1.1 client should accept a 1.1 broker
45
45
  Given a Stomp 1.1 broker
46
46
  When the client protocol version is "1.1"
47
- And a connection is established
47
+ And the connection is told to connect
48
48
  Then the connection should be using the 1.1 protocol
49
49
 
50
50
  Scenario: A 1.1 client should not accept a 1.0 broker
@@ -38,4 +38,12 @@ end
38
38
 
39
39
  Then /^the client nacking the last MESSAGE should raise an argument error$/ do
40
40
  lambda { @connection.nack @received_frames.select { |f| f.command == "MESSAGE" }.last }.should raise_error(ArgumentError)
41
- end
41
+ end
42
+
43
+ When /^the client acks a message by ID "([^"]*)" and subscription "([^"]*)" within the scope$/ do |message_id, subscription|
44
+ @scope.ack message_id, subscription
45
+ end
46
+
47
+ When /^the client nacks a message by ID "([^"]*)" and subscription "([^"]*)" within the scope$/ do |message_id, subscription|
48
+ @scope.nack message_id, subscription
49
+ end
@@ -0,0 +1,52 @@
1
+ Given /^a Stomp (\d+\.\d+)?\s*broker$/ do |version|
2
+ version ||= '1.0'
3
+ @broker_uri_string = "stomp:///"
4
+ @broker_uri = URI.parse(@broker_uri_string)
5
+ @broker = TestStompServer.new(version)
6
+ @broker.start
7
+ @connection = Stomper::Connection.new(@broker_uri)
8
+ end
9
+
10
+ Given /^an erroring Stomp broker$/ do
11
+ @broker_uri_string = "stomp:///"
12
+ @broker_uri = URI.parse(@broker_uri_string)
13
+ @broker = TestStompServer.new('1.0')
14
+ @broker.session_class = TestStompServer::StompErrorOnConnectSession
15
+ @broker.start
16
+ @connection = Stomper::Connection.new(@broker_uri)
17
+ end
18
+
19
+ Then /^the broker should have received an? "([^"]*)" frame$/ do |command|
20
+ Then "the broker should have received a \"#{command}\" frame with headers", table(%{
21
+ | header-name | header-value |
22
+ })
23
+ end
24
+
25
+ Then /^the broker should have received an? "([^"]*)" frame with headers$/ do |command, table|
26
+ headers = table_to_headers table
27
+ @broker.session.received_frames.any? do |f|
28
+ f.command == command && headers.all? { |(k,v)| headers[k] == f[k] }
29
+ end.should be_true
30
+ end
31
+
32
+ When /^the broker sends a "([^"]*)" frame with headers$/ do |command, table|
33
+ headers = table_to_headers table
34
+ @broker.session.send_frame command, headers
35
+ end
36
+
37
+ When /^the broker closes the connection unexpectedly$/ do
38
+ @broker.force_stop
39
+ end
40
+
41
+ Given /^a Stomp (\d+\.\d+)?\s*SSL broker$/ do |version|
42
+ @broker = TestSSLStompServer.new(version)
43
+ @broker.start
44
+ end
45
+
46
+ Given /^an unversioned Stomp broker$/ do
47
+ @broker_uri_string = "stomp:///"
48
+ @broker_uri = URI.parse(@broker_uri_string)
49
+ @broker = TestStompServer.new(nil)
50
+ @broker.start
51
+ @connection = Stomper::Connection.new(@broker_uri)
52
+ end
@@ -0,0 +1,137 @@
1
+ After do |s|
2
+ begin
3
+ @connection && @connection.stop
4
+ @broker && @broker.force_stop
5
+ rescue Exception => ex
6
+ end
7
+ end
8
+
9
+ Given /^a (\d+\.\d+)?\s*connection between client and broker$/ do |version|
10
+ version ||= '1.0'
11
+ @broker_uri_string = "stomp:///"
12
+ @broker_uri = URI.parse(@broker_uri_string)
13
+ @broker = TestStompServer.new(version)
14
+ @broker.start
15
+ @connection = Stomper::Connection.new(@broker_uri)
16
+ @received_frames = []
17
+ @sent_frames = []
18
+ @connection.before_transmitting do |f, c|
19
+ @sent_frames << f
20
+ end
21
+ @connection.after_receiving do |f, c|
22
+ @received_frames << f
23
+ end
24
+ @connection.start
25
+ end
26
+
27
+ When /^the frame exchange is completed$/ do
28
+ @connection.disconnect(:receipt => 'TERMINATE_POLITELY_12345')
29
+ @connection.stop
30
+ @broker.stop
31
+ end
32
+
33
+ When /^the frame exchange is completed without client disconnect$/ do
34
+ @connection.stop
35
+ @broker.stop
36
+ end
37
+
38
+ When /^a connection is created from the broker's URI$/ do
39
+ #@connection = Stomper::Connection.new(@broker_uri)
40
+ end
41
+
42
+ When /^a connection is created from the broker's URI string$/ do
43
+ @connection = Stomper::Connection.new(@broker_uri_string)
44
+ end
45
+
46
+ When /^the connection is told to connect$/ do
47
+ @connection.connect
48
+ end
49
+
50
+ When /^the client protocol version is "([^"]*)"$/ do |arg1|
51
+ @connection.versions = arg1.split(",")
52
+ end
53
+
54
+ Then /^the connection should be connected$/ do
55
+ @connection.connected?.should be_true
56
+ end
57
+
58
+ Then /^the connection should be using the (\d+\.\d+) protocol$/ do |version|
59
+ @connection.version.should == version
60
+ end
61
+
62
+ Then /^connecting should raise an unsupported protocol version error$/ do
63
+ lambda { @connection.connect }.should raise_error(Stomper::Errors::UnsupportedProtocolVersionError)
64
+ end
65
+
66
+ Then /^the (connection|client) should not be connected$/ do |arbitrary_name|
67
+ @connection.connected?.should be_false
68
+ end
69
+
70
+ Then /^connecting should raise an connect failed error$/ do
71
+ lambda { @connection.connect }.should raise_error(Stomper::Errors::ConnectFailedError)
72
+ end
73
+
74
+ Then /^the client should have received an? "([^"]*)" frame with headers$/ do |command, table|
75
+ headers = table_to_headers table
76
+ @received_frames.any? do |f|
77
+ f.command == command && headers.all? { |(k,v)| headers[k] == f[k] }
78
+ end.should be_true
79
+ end
80
+
81
+ When /^the client waits for (\d+) "([^"]*)" frames?$/ do |count, command|
82
+ count = count.to_i
83
+ Thread.pass while @received_frames.select { |f| f.command == command }.size < count
84
+ end
85
+
86
+ Given /^an established connection$/ do
87
+ @connection.connect
88
+ end
89
+
90
+ When /^the client disconnects$/ do
91
+ @connection.disconnect
92
+ @broker.stop
93
+ end
94
+
95
+ Then /^after (\d+\.\d+) seconds, the receiver should no longer be running$/ do |sleep_for|
96
+ sleep sleep_for.to_f
97
+ @connection.running?.should be_false
98
+ end
99
+
100
+ When /^a connection is created for the SSL broker$/ do
101
+ @connection = Stomper::Connection.new("stomp+ssl:///")
102
+ end
103
+
104
+ When /^the broker's host is "([^"]*)"$/ do |hostname|
105
+ @connection.host = hostname
106
+ end
107
+
108
+ When /^no SSL verification is performed$/ do
109
+ @connection.ssl[:verify_mode] = OpenSSL::SSL::VERIFY_NONE
110
+ end
111
+
112
+ When /^SSL verification is performed$/ do
113
+ @connection.ssl[:verify_mode] = ::OpenSSL::SSL::VERIFY_PEER | ::OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT
114
+ end
115
+
116
+ When /^an SSL post connection check is performed on "([^"]*)"$/ do |host|
117
+ @connection.ssl[:post_connection_check] = host
118
+ end
119
+
120
+ Then /^connecting should raise an openssl error$/ do
121
+ lambda { @connection.connect }.should raise_error(OpenSSL::SSL::SSLError)
122
+ # It is problematic that this is needed...
123
+ @broker.stop
124
+ end
125
+
126
+ When /^an SSL post connection check is not performed$/ do
127
+ @connection.ssl[:post_connection_check] = false
128
+ end
129
+
130
+ When /^the broker's certificate is verified by CA$/ do
131
+ @connection.ssl[:ca_file] = File.expand_path('../../support/ssl/demoCA/cacert.pem', __FILE__)
132
+ end
133
+
134
+ When /^the client's certificate and key are specified$/ do
135
+ @connection.ssl[:cert] = OpenSSL::X509::Certificate.new(File.read(File.expand_path('../../support/ssl/client_cert.pem', __FILE__)))
136
+ @connection.ssl[:key] = OpenSSL::PKey::RSA.new(File.read(File.expand_path('../../support/ssl/client_key.pem', __FILE__)))
137
+ end
@@ -3,19 +3,6 @@ Given /^a header scope with headers$/ do |table|
3
3
  @scope = @connection.with_headers(headers)
4
4
  end
5
5
 
6
- When /^the client acks a message by ID "([^"]*)" and subscription "([^"]*)" within the scope$/ do |message_id, subscription|
7
- @scope.ack message_id, subscription
8
- end
9
-
10
- When /^the client subscribes to "([^"]*)" with headers within the scope$/ do |dest, table|
11
- @subscribe_frames ||= []
12
- headers = table_to_headers table
13
- @default_subscription_triggered = 0
14
- @subscribe_frames << @scope.subscribe(dest, headers) do |m|
15
- @default_subscription_triggered += 1
16
- end
17
- end
18
-
19
6
  Given /^a transaction scope named "([^"]*)"$/ do |tx|
20
7
  @scope = @connection.with_transaction(:transaction => tx)
21
8
  end
@@ -24,9 +11,6 @@ When /^the client begins the transaction scope$/ do
24
11
  @scope.begin
25
12
  end
26
13
 
27
- When /^the client nacks a message by ID "([^"]*)" and subscription "([^"]*)" within the scope$/ do |message_id, subscription|
28
- @scope.nack message_id, subscription
29
- end
30
14
 
31
15
  When /^the client aborts the transaction scope$/ do
32
16
  @scope.abort
@@ -0,0 +1,80 @@
1
+ When /^the client subscribes to "([^"]*)" with headers$/ do |dest, table|
2
+ @subscribe_frames ||= []
3
+ headers = table_to_headers table
4
+ @default_subscription_triggered = 0
5
+ @subscribe_frames << @connection.subscribe(dest, headers) do |m|
6
+ @default_subscription_triggered += 1
7
+ end
8
+ end
9
+
10
+ Then /^the default subscription callback should have been triggered( (\d+) times?)?$/ do |full, times|
11
+ if times.nil? || times.empty?
12
+ @default_subscription_triggered.should >= 1
13
+ else
14
+ @default_subscription_triggered.should == times.to_i
15
+ end
16
+ end
17
+
18
+ Then /^the default subscription callback should not have been triggered$/ do
19
+ @default_subscription_triggered.should == 0
20
+ end
21
+
22
+ When /^the client unsubscribes by ID$/ do
23
+ @connection.unsubscribe(@subscribe_frames.last[:id])
24
+ end
25
+
26
+ When /^the client unsubscribes by destination$/ do
27
+ @connection.unsubscribe(@subscribe_frames.last[:destination])
28
+ end
29
+
30
+ When /^the client unsubscribes by frame$/ do
31
+ @connection.unsubscribe(@subscribe_frames.last)
32
+ end
33
+
34
+ When /^the client unsubscribes from destination "([^"]*)"$/ do |destination|
35
+ @connection.unsubscribe(destination)
36
+ end
37
+
38
+ Given /^the client subscribes to (\/.*)$/ do |dest|
39
+ @messages_for_subscription ||= []
40
+ @connection.subscribe(dest) do |m|
41
+ sub = m[:subscription]
42
+ @messages_for_subscription << m
43
+ end
44
+ end
45
+
46
+ When /^the client sends a "([^"]*)" "([^"]*)" to (\/.*)$/ do |ct, body, dest|
47
+ @connection.send(dest, body, :'content-type' => ct)
48
+ end
49
+
50
+ Then /^the client should have received a "([^"]*)" message of "([^"]*)"$/ do |ct, body|
51
+ @messages_for_subscription.any? do |m|
52
+ m.content_type == ct && m.body == body
53
+ end.should be_true
54
+ end
55
+
56
+ When /^the client sends a "([^"]*)" encoded as "([^"]*)" to (\/.*)$/ do |body, enc, dest|
57
+ body.force_encoding(enc) if body.respond_to?(:force_encoding)
58
+ @connection.send(dest, body)
59
+ end
60
+
61
+ Then /^the client should have received a "([^"]*)" message of "([^"]*)" encoded as "([^"]*)"$/ do |ct, body, enc|
62
+ @messages_for_subscription.any? do |m|
63
+ ct_check = (m.content_type == ct || m.content_type.nil? && ct.empty?)
64
+ b_check = body == m.body
65
+ if body.respond_to?(:encoding)
66
+ ct_check && b_check && m.body.encoding.name == enc
67
+ else
68
+ ct_check && b_check
69
+ end
70
+ end.should be_true
71
+ end
72
+
73
+ When /^the client subscribes to "([^"]*)" with headers within the scope$/ do |dest, table|
74
+ @subscribe_frames ||= []
75
+ headers = table_to_headers table
76
+ @default_subscription_triggered = 0
77
+ @subscribe_frames << @scope.subscribe(dest, headers) do |m|
78
+ @default_subscription_triggered += 1
79
+ end
80
+ end
@@ -171,7 +171,7 @@ class Stomper::Connection
171
171
  @connecting = false
172
172
  @disconnecting = false
173
173
  @disconnected = false
174
- @socket_mutex = ::Mutex.new
174
+ @close_mutex = ::Mutex.new
175
175
 
176
176
  on_connected do |cf, con|
177
177
  unless connected?
@@ -272,34 +272,32 @@ class Stomper::Connection
272
272
  # connection has been established and you're ready to go, otherwise the
273
273
  # socket will be closed and an error will be raised.
274
274
  def connect(headers={})
275
- #@socket_mutex.synchronize do
276
- unless @connected
277
- @socket = @uri.create_socket(@ssl)
278
- @serializer = ::Stomper::FrameSerializer.new(@socket)
279
- m_headers = {
280
- :'accept-version' => @versions.join(','),
281
- :host => @host,
282
- :'heart-beat' => @heartbeats.join(','),
283
- :login => @login,
284
- :passcode => @passcode
285
- }
286
- @disconnecting = false
287
- @disconnected = false
288
- @connecting = true
289
- transmit create_frame('CONNECT', headers, m_headers)
290
- receive.tap do |f|
291
- if f.command == 'CONNECTED'
292
- @connected_frame = f
293
- @connected = true
294
- @connecting = false
295
- trigger_event(:on_connection_established, self)
296
- else
297
- close
298
- raise ::Stomper::Errors::ConnectFailedError, 'broker did not send CONNECTED frame'
299
- end
275
+ unless @connected
276
+ @socket = @uri.create_socket(@ssl)
277
+ @serializer = ::Stomper::FrameSerializer.new(@socket)
278
+ m_headers = {
279
+ :'accept-version' => @versions.join(','),
280
+ :host => @host,
281
+ :'heart-beat' => @heartbeats.join(','),
282
+ :login => @login,
283
+ :passcode => @passcode
284
+ }
285
+ @disconnecting = false
286
+ @disconnected = false
287
+ @connecting = true
288
+ transmit create_frame('CONNECT', headers, m_headers)
289
+ receive.tap do |f|
290
+ if f.command == 'CONNECTED'
291
+ @connected_frame = f
292
+ @connected = true
293
+ @connecting = false
294
+ trigger_event(:on_connection_established, self)
295
+ else
296
+ close
297
+ raise ::Stomper::Errors::ConnectFailedError, 'broker did not send CONNECTED frame'
300
298
  end
301
299
  end
302
- #end
300
+ end
303
301
  end
304
302
  alias :open :connect
305
303
 
@@ -372,7 +370,7 @@ class Stomper::Connection
372
370
  # @param [true,false] fire_terminated (false) If true, trigger
373
371
  # {Stomper::Extensions::Events#on_connection_terminated}
374
372
  def close
375
- #@socket_mutex.synchronize do
373
+ @close_mutex.synchronize do
376
374
  if @connected
377
375
  begin
378
376
  trigger_event(:on_connection_terminated, self) unless @disconnected
@@ -384,8 +382,10 @@ class Stomper::Connection
384
382
  @connected = false
385
383
  end
386
384
  trigger_event(:on_connection_closed, self)
385
+ subscription_manager.clear
386
+ receipt_manager.clear
387
387
  end
388
- #end
388
+ end
389
389
  end
390
390
 
391
391
  # Transmits a frame to the broker. This is a low-level method used internally
@@ -27,6 +27,11 @@ class Stomper::ReceiptManager
27
27
  self
28
28
  end
29
29
 
30
+ # Remove all receipt handlers.
31
+ def clear
32
+ @mon.synchronize { @callbacks.clear }
33
+ end
34
+
30
35
  private
31
36
  def dispatch(receipt)
32
37
  cb = @mon.synchronize { @callbacks.delete(receipt[:'receipt-id']) }
@@ -9,7 +9,6 @@ class Stomper::SubscriptionManager
9
9
  @subscriptions = {}
10
10
  connection.on_message { |m, con| dispatch(m) }
11
11
  connection.on_unsubscribe { |u, con| remove(u[:id]) }
12
- connection.on_connection_closed { |con| @subscriptions.clear }
13
12
  end
14
13
 
15
14
  # Adds a callback handler for a MESSAGE frame that is sent via the subscription
@@ -46,12 +45,18 @@ class Stomper::SubscriptionManager
46
45
  end
47
46
  end
48
47
 
49
- # Returns all current subscriptions in the form of their SUBSCRIBE frames.
50
- # @return [Array<Stomper::Frame>]
48
+ # Returns all current subscriptions.
49
+ # @return [Array<Stomper::SubscriptionManager::Subscription>]
51
50
  def subscriptions
52
51
  @mon.synchronize { @subscriptions.values }
53
52
  end
54
53
 
54
+ # Remove all subscriptions. This method does not send UNSUBSCRIBE frames
55
+ # to the broker.
56
+ def clear
57
+ @mon.synchronize { @subscriptions.clear }
58
+ end
59
+
55
60
  private
56
61
  def dispatch(message)
57
62
  s_id = message[:subscription]
@@ -3,5 +3,5 @@
3
3
  # Primary namespace of the stomper gem.
4
4
  module Stomper
5
5
  # The current version of the stomper gem.
6
- VERSION = '2.0.1'
6
+ VERSION = '2.0.2'
7
7
  end
@@ -218,6 +218,22 @@ module Stomper
218
218
  @connection.connected?.should be_false
219
219
  end
220
220
 
221
+ it "should clear subscriptions on close" do
222
+ @serializer.should_receive(:read_frame).at_least(:once).and_return(@connected_frame)
223
+ @serializer.should_receive(:write_frame).with(stomper_frame_with_headers({}, 'CONNECT')).once.and_return { |f| f }
224
+ @connection.connect
225
+ @connection.subscription_manager.should_receive(:clear)
226
+ @connection.close
227
+ end
228
+
229
+ it "should clear receipt handlers on close" do
230
+ @serializer.should_receive(:read_frame).at_least(:once).and_return(@connected_frame)
231
+ @serializer.should_receive(:write_frame).with(stomper_frame_with_headers({}, 'CONNECT')).once.and_return { |f| f }
232
+ @connection.connect
233
+ @connection.receipt_manager.should_receive(:clear)
234
+ @connection.close
235
+ end
236
+
221
237
  describe "frame reading" do
222
238
  before(:each) do
223
239
  @serializer.should_receive(:read_frame).at_least(:once).and_return(@connected_frame)
@@ -394,7 +410,6 @@ module Stomper
394
410
  lambda { @connection.receive }.should raise_error(SystemCallError)
395
411
  triggered.should be_true
396
412
  end
397
-
398
413
  end
399
414
 
400
415
  describe "frame events" do