klomp 0.0.4 → 0.0.5

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.
@@ -1,6 +1,11 @@
1
1
  Changes
2
2
  --------------------------------------------------------------------------------
3
3
 
4
+ 0.0.5 (2012/8/10)
5
+ ================================================================================
6
+
7
+ - set message send/receive logging to debug instead of info
8
+
4
9
  0.0.4 (2012/6/22)
5
10
  ================================================================================
6
11
 
data/Gemfile CHANGED
@@ -2,6 +2,7 @@ source "http://rubygems.org"
2
2
 
3
3
  gem "onstomp", "~> 1.0.7"
4
4
  # gem 'onstomp', :path => '../onstomp'
5
+ gem "stomp", "~> 1.2.4"
5
6
  gem "json"
6
7
  gem "uuid", "~> 2.3.5"
7
8
 
@@ -9,6 +9,7 @@ GEM
9
9
  systemu (~> 2.5.0)
10
10
  onstomp (1.0.7)
11
11
  rake (0.9.2.2)
12
+ stomp (1.2.4)
12
13
  systemu (2.5.1)
13
14
  thor (0.15.2)
14
15
  uuid (2.3.5)
@@ -23,4 +24,5 @@ DEPENDENCIES
23
24
  json
24
25
  onstomp (~> 1.0.7)
25
26
  rake
27
+ stomp (~> 1.2.4)
26
28
  uuid (~> 2.3.5)
data/README.md CHANGED
@@ -1,16 +1,16 @@
1
1
  # Klomp
2
2
 
3
- Klomp is a simple wrapper around the [OnStomp](https://github.com/meadvillerb/onstomp/)
4
- library with some additional HA and usability features:
3
+ Klomp is a simple wrapper around the [Stomp](https://github.com/stompgem/stomp)
4
+ and [OnStomp](https://github.com/meadvillerb/onstomp/) libraries with some
5
+ additional HA and usability features:
5
6
 
6
7
  * When initialized with multiple broker URIs, Klomp will publish messages to
7
8
  one broker at a time, but will consume from all brokers simultaneously. This is
8
- a slight improvement over the regular [OnStomp::Failover::Client](http://mdvlrb.com/onstomp/OnStomp/Failover/Client.html)
9
- which handles all publishing and subscribing through a single "active" broker.
10
- This traditional one-broker-at-a-time technique can lead to a split-brain
11
- scenario in which messages are only received by a subset of your STOMP clients.
12
- By consuming from all brokers simultaneously, Klomp ensures that no message is
13
- left behind.
9
+ a slight improvement over traditional failover clients that work by publishing
10
+ and subscribing through a single "active" broker. This one-broker-at-a-time
11
+ technique can lead to a split-brain scenario in which messages are only
12
+ received by a subset of your STOMP clients. By consuming from all brokers
13
+ simultaneously, Klomp ensures that no message is left behind.
14
14
 
15
15
  * Where applicable, message bodies are automatically translated between native
16
16
  Ruby and JSON objects.
@@ -26,14 +26,13 @@ subscribe block.
26
26
  ## Example usage
27
27
 
28
28
  The goal is that you should be able to use most (if not all) of the standard
29
- OnStomp API (see [OnStomp's UserNarrative](https://github.com/meadvillerb/onstomp/blob/master/extra_doc/UserNarrative.md))
30
- via a `Klomp::Client`:
29
+ Stomp/OnStomp APIs via a `Klomp::Client`:
31
30
 
32
31
  client = Klomp::Client.new([ ... ])
33
32
 
34
33
  However, there will be some differences in the API due to how `Klomp::Client`
35
- manages connections. For example, while the `connected?` method normally
36
- returns a boolean value, Klomp's `connected?` will return an array of booleans
34
+ manages connections. For example, while OnStomp's `connected?` method normally
35
+ returns a single boolean value, Klomp's `connected?` will return many booleans
37
36
  (i.e. one result for each broker).
38
37
 
39
38
  ### Fibonacci back-off retry behavior
@@ -59,6 +58,11 @@ pass implements a `#generate` method that returns a string ID.
59
58
  <th>Default value</th>
60
59
  <th>Description</th>
61
60
  </tr>
61
+ <tr>
62
+ <td>:adapter</td>
63
+ <td>:onstomp</td>
64
+ <td>Stomp library to use. Currently, only :stomp and :onstomp are supported.</td>
65
+ </tr>
62
66
  <tr>
63
67
  <td>:translate_json</td>
64
68
  <td>true</td>
@@ -75,9 +79,9 @@ pass implements a `#generate` method that returns a string ID.
75
79
  <td>Logger object</td>
76
80
  </tr>
77
81
  <tr>
78
- <td>:uuid</td>
79
- <td>UUID.new</td>
80
- <td>UUID generator object, responds to :generate and returns an ID</td>
82
+ <td>:uuid</td>
83
+ <td>UUID.new</td>
84
+ <td>UUID generator object, responds to :generate and returns an ID</td>
81
85
  </tr>
82
86
  </table>
83
87
 
@@ -102,9 +106,7 @@ failover.
102
106
 
103
107
  ## Change Log
104
108
 
105
- ### 0.0.1
106
-
107
- * Initial release
109
+ See the [ChangeLog file](/livingsocial/klomp/blob/master/ChangeLog.md).
108
110
 
109
111
  ## License
110
112
 
@@ -1,5 +1,5 @@
1
1
  require 'klomp/client'
2
2
 
3
3
  module Klomp
4
- VERSION = '0.0.4'
4
+ VERSION = '0.0.5'
5
5
  end
@@ -1,21 +1,50 @@
1
1
  require 'onstomp'
2
2
  require 'onstomp/failover'
3
+ require 'stomp'
3
4
  require 'json'
4
5
  require 'uuid'
5
6
  require 'logger'
7
+ require 'pp'
6
8
 
7
9
  class OnStomp::Failover::Client
8
10
  # Save previous N, N-1 delays for fibonacci backoff
9
11
  attr_accessor :prev_retry_delay
10
12
  end
11
13
 
14
+ class Hash
15
+ def to_query_string
16
+ self.inject([]) { |memo,(k,v)| memo + ["#{k}=#{v}"] }.join('&')
17
+ end
18
+ end
19
+
12
20
  module Klomp
13
21
 
14
22
  class Client
15
- attr_reader :read_conn, :write_conn
23
+ attr_reader :read_conn, :write_conn, :all_conn
16
24
  attr_accessor :last_connect_exception
17
25
 
26
+ WRITE_ONLY_METHODS = [
27
+ :abort,
28
+ :begin,
29
+ :commit,
30
+ ]
31
+
32
+ READ_ONLY_METHODS = [
33
+ :ack,
34
+ :nack,
35
+ ]
36
+
37
+ TRANSLATE_METHODS = {
38
+ :connected? => {
39
+ :stomp => :open?
40
+ },
41
+ :ack => {
42
+ :stomp => :acknowledge
43
+ }
44
+ }
45
+
18
46
  def initialize(uri, options={})
47
+ @adapter = options.fetch(:adapter, :onstomp)
19
48
  @translate_json = options.fetch(:translate_json, true)
20
49
  @auto_reply_to = options.fetch(:auto_reply_to, true)
21
50
  @logger = options.fetch(:logger, nil)
@@ -27,44 +56,91 @@ module Klomp
27
56
  options[:retry_delay] ||= 1
28
57
  options[:retry_attempts] ||= -1
29
58
 
30
- if uri.is_a?(Array)
31
- @write_conn = OnStomp::Failover::Client.new(uri, options)
32
- @read_conn = uri.map {|obj| OnStomp::Failover::Client.new([obj], options) }
59
+ case @adapter
60
+ when :onstomp
61
+ if uri.is_a?(Array)
62
+ @write_conn = OnStomp::Failover::Client.new(uri, options)
63
+ @read_conn = uri.map {|obj| OnStomp::Failover::Client.new([obj], options) }
64
+ else
65
+ @write_conn = OnStomp::Failover::Client.new([uri], options)
66
+ @read_conn = [@write_conn]
67
+ end
68
+ when :stomp
69
+ # Failover in the Stomp library is kind of flaky. If the client
70
+ # temporarily loses its connection, it is eventually able to reconnect
71
+ # and resume sending messages. However, the subscribe thread never
72
+ # seems to recover. One workaround I discovered is to always create new
73
+ # clients with the following URL scheme:
74
+ #
75
+ # failover:(stomp://localhost,stomp://localhost)
76
+ #
77
+ # Notice that we're using a failover URL in which the same host is
78
+ # specified twice. It's a pretty silly hack, but it's the only way I've
79
+ # been able to get failover to work reliably.
80
+ #
81
+ # - Mike Conigliaro
82
+ #
83
+ if uri.is_a?(Array)
84
+ @write_conn = Stomp::Client.new("failover:(#{uri.join(',')})?#{options.to_query_string}")
85
+ @read_conn = uri.map {|obj| Stomp::Client.new("failover:(#{obj},#{obj})?#{options.to_query_string}") }
86
+ else
87
+ @write_conn = Stomp::Client.new("failover:(#{uri},#{uri})?#{options.to_query_string}")
88
+ @read_conn = [@write_conn]
89
+ end
33
90
  else
34
- @write_conn = OnStomp::Failover::Client.new([uri], options)
35
- @read_conn = [@write_conn]
91
+ raise ArgumentError, "Klomp does not support the #{@adapter} library"
36
92
  end
37
93
  @all_conn = ([@write_conn] + @read_conn).uniq
38
94
  configure_connections
39
95
  end
40
96
 
41
97
  def connect
42
- @all_conn.each do |conn|
43
- begin
44
- attempts = conn.retry_attempts
45
- conn.retry_attempts = 1
46
- conn.connect
47
- rescue OnStomp::Failover::MaximumRetriesExceededError
48
- location = conn.active_client.uri.dup.tap {|u| u.password = 'REDACTED' }.to_s
49
- msg = ": #{last_connect_exception.message}" if last_connect_exception
50
- raise OnStomp::ConnectFailedError, "initial connection failed for #{location}#{msg}"
51
- ensure
52
- conn.retry_attempts = attempts
98
+ case @adapter
99
+ when :onstomp
100
+ @all_conn.each do |conn|
101
+ begin
102
+ attempts = conn.retry_attempts
103
+ conn.retry_attempts = 1
104
+ conn.connect
105
+ rescue OnStomp::Failover::MaximumRetriesExceededError
106
+ location = conn.active_client.uri.dup.tap {|u| u.password = 'REDACTED' }.to_s
107
+ msg = ": #{last_connect_exception.message}" if last_connect_exception
108
+ raise OnStomp::ConnectFailedError, "initial connection failed for #{location}#{msg}"
109
+ ensure
110
+ conn.retry_attempts = attempts
111
+ end
53
112
  end
54
113
  end
55
114
  self
56
115
  end
57
116
 
58
- def send(dest, body, headers={}, &cb)
117
+ def disconnect
118
+ method = case @adapter
119
+ when :onstomp
120
+ :disconnect
121
+ when :stomp
122
+ :close
123
+ end
124
+ @all_conn.inject({}) { |memo,obj| memo.update({ obj => obj.__send__(method) }) }
125
+ end
126
+
127
+ def send(dest, body, headers={}, &block)
59
128
  if @translate_json && body.respond_to?(:to_json)
60
129
  body = body.to_json
61
- headers[:'content-type'] = 'application/json'
130
+ headers['content-type'] = 'application/json'
62
131
  else
63
132
  body = body.to_s
64
133
  end
65
- uuid = headers[:id] = @uuid.generate if @uuid
66
- log.info("[Sending] ID=#{uuid} Destination=#{dest} Body=#{body.inspect} Headers=#{headers.inspect}") if log
67
- @write_conn.send(dest, body, headers, &cb)
134
+ uuid = headers['id'] = @uuid.generate if @uuid
135
+ log.debug("[Sending] ID=#{uuid} Destination=#{dest} Body=#{body.inspect} Headers=#{headers.inspect}") if log
136
+
137
+ method = case @adapter
138
+ when :onstomp
139
+ :send
140
+ when :stomp
141
+ :publish
142
+ end
143
+ @write_conn.__send__(method, dest, body, headers, &block)
68
144
  end
69
145
  alias publish send
70
146
 
@@ -72,7 +148,7 @@ module Klomp
72
148
  frames = []
73
149
  @read_conn.each do |c|
74
150
  frames << c.subscribe(*args) do |msg|
75
- log.info("[Received] ID=#{msg[:id]} Body=#{msg.body.inspect} Headers=#{msg.headers.to_hash.inspect}") if log
151
+ log.debug("[Received] ID=#{msg.headers['id']} Body=#{msg.body.inspect} Headers=#{msg.headers.to_hash.inspect}") if log
76
152
  if @translate_json
77
153
  msg.body = begin
78
154
  JSON.parse(msg.body)
@@ -81,11 +157,11 @@ module Klomp
81
157
  end
82
158
  end
83
159
  reply_args = yield msg
84
- if @auto_reply_to && !msg.headers[:'reply-to'].nil?
160
+ if @auto_reply_to && !msg.headers['reply-to'].nil?
85
161
  if reply_args.is_a?(Array)
86
- send(msg.headers[:'reply-to'], *reply_args)
162
+ send(msg.headers['reply-to'], *reply_args)
87
163
  else
88
- send(msg.headers[:'reply-to'], reply_args)
164
+ send(msg.headers['reply-to'], reply_args)
89
165
  end
90
166
  end
91
167
  end
@@ -94,11 +170,16 @@ module Klomp
94
170
  end
95
171
 
96
172
  def unsubscribe(frames, headers={})
97
- if !frames.respond_to?(:length) || frames.length != @read_conn.length
98
- raise ArgumentError,
99
- "frames is not an array or its length does not match number of connections"
173
+ case @adapter
174
+ when :onstomp
175
+ if !frames.respond_to?(:length) || frames.length != @read_conn.length
176
+ raise ArgumentError,
177
+ "frames is not an array or its length does not match number of connections"
178
+ end
179
+ frames.each_with_index.map {|f,i| @read_conn[i].unsubscribe f, headers }
180
+ else
181
+ @read_conn.each { |obj| obj.unsubscribe(frames, headers) }
100
182
  end
101
- frames.each_with_index.map {|f,i| @read_conn[i].unsubscribe f, headers }
102
183
  end
103
184
 
104
185
  def subscriptions
@@ -109,47 +190,45 @@ module Klomp
109
190
  @logger
110
191
  end
111
192
 
112
- WRITE_ONLY_METHODS = [
113
- :abort,
114
- :begin,
115
- :commit,
116
- ]
117
-
118
- READ_ONLY_METHODS = [
119
- :ack,
120
- :nack,
121
- ]
122
-
123
193
  def method_missing(method, *args, &block)
194
+ if TRANSLATE_METHODS.has_key?(method) && TRANSLATE_METHODS[method].has_key?(@adapter)
195
+ method = TRANSLATE_METHODS[method][@adapter]
196
+ end
197
+
124
198
  case method
125
199
  when *WRITE_ONLY_METHODS
126
200
  @write_conn.__send__(method, *args, &block)
127
201
  when *READ_ONLY_METHODS
128
- @read_conn.map {|c| c.__send__(method, *args, &block) }
202
+ @read_conn.inject({}) { |memo,obj| memo.update({ obj => obj.__send__(method, *args, &block) }) }
129
203
  else
130
- @all_conn.map {|c| c.__send__(method, *args) }
204
+ @all_conn.inject({}) { |memo,obj| memo.update({ obj => obj.__send__(method, *args, &block) }) }
131
205
  end
132
206
  end
133
207
 
134
208
  private
209
+
135
210
  def configure_connections
136
- klomp_client = self
137
- @all_conn.each do |c|
138
- if @fib_retry_backoff
139
- c.before_failover_retry do |conn, attempt|
140
- if attempt == 1
141
- conn.prev_retry_delay, conn.retry_delay = 0, 1
142
- else
143
- conn.prev_retry_delay, conn.retry_delay = conn.retry_delay, conn.prev_retry_delay + conn.retry_delay
211
+ case @adapter
212
+ when :onstomp
213
+ klomp_client = self
214
+ @all_conn.each do |c|
215
+ if @fib_retry_backoff
216
+ c.before_failover_retry do |conn, attempt|
217
+ if attempt == 1
218
+ conn.prev_retry_delay, conn.retry_delay = 0, 1
219
+ else
220
+ conn.prev_retry_delay, conn.retry_delay = conn.retry_delay, conn.prev_retry_delay + conn.retry_delay
221
+ end
144
222
  end
145
223
  end
146
- end
147
224
 
148
- c.on_failover_connect_failure do
149
- klomp_client.last_connect_exception = $!
225
+ c.on_failover_connect_failure do
226
+ klomp_client.last_connect_exception = $!
227
+ end
150
228
  end
151
229
  end
152
230
  end
231
+
153
232
  end
154
233
 
155
234
  end
@@ -10,12 +10,12 @@ task :test_failover do
10
10
  client = Klomp::Client.new([
11
11
  'stomp://admin:password@localhost:61613',
12
12
  'stomp://admin:password@127.0.0.1:62613'
13
- ]).connect
13
+ ], :adapter => :onstomp).connect
14
14
 
15
15
  last_i = nil
16
16
  client.subscribe("/queue/test") do |msg|
17
17
  print "-"
18
- last_i = msg.body.to_i
18
+ last_i = msg.body.gsub('"', '').to_i
19
19
  end
20
20
 
21
21
  begin
@@ -0,0 +1,167 @@
1
+ require 'minitest/autorun'
2
+ require 'minitest/pride'
3
+
4
+ require 'klomp'
5
+ require File.expand_path('../test_helper', __FILE__)
6
+
7
+ describe Klomp::Client do
8
+
9
+ include KlompTestHelpers
10
+
11
+ before do
12
+ @uris = [
13
+ 'stomp://admin:password@localhost:61613',
14
+ 'stomp://admin:password@127.0.0.1:62613'
15
+ ]
16
+ @destination = '/queue/test_component.test_event'
17
+ end
18
+
19
+ [ :onstomp, :stomp ].each do |adapter|
20
+
21
+ it "(#{adapter}) has a logger" do
22
+ logger = Logger.new(STDOUT)
23
+ client = Klomp::Client.new(@uris, :adapter => adapter, :logger => logger)
24
+ assert_equal client.log, logger
25
+ end
26
+
27
+ it "(#{adapter}) sends all unknown options through to the underlying library" do
28
+ client = Klomp::Client.new(@uris.first, :adapter => adapter, :haz_cheezburgers => true, :retry_attempts => 42).connect
29
+ assert client.connected?.values.all?
30
+ assert_equal 42, client.write_conn.retry_attempts if adapter == :onstomp
31
+ client.disconnect
32
+ end
33
+
34
+ it "(#{adapter}) accepts a single uri and establishes separate failover connections for writes and reads" do
35
+ client = Klomp::Client.new(@uris.first, :adapter => adapter).connect
36
+ assert_equal [client.write_conn], client.read_conn
37
+ assert client.connected?.values.all?
38
+ client.disconnect
39
+ end
40
+
41
+ it "(#{adapter}) accepts an array of uris and establishes separate failover connections for writes and reads" do
42
+ client = Klomp::Client.new(@uris, :adapter => adapter).connect
43
+ assert client.all_conn.length == @uris.length + 1
44
+ assert client.connected?.values.all?
45
+ client.disconnect
46
+ end
47
+
48
+ it "(#{adapter}) disconnnects" do
49
+ client = Klomp::Client.new(@uris.first, :adapter => adapter).connect
50
+ assert client.connected?.values.all?
51
+ client.disconnect
52
+ refute client.connected?.values.any?
53
+ end
54
+
55
+ it "(#{adapter}) sends requests and gets responses" do
56
+ client = Klomp::Client.new(@uris, :adapter => adapter).connect
57
+ body = { 'body' => rand(36**128).to_s(36) }
58
+
59
+ client.send(@destination, body, :ack=>'client')
60
+ got_message = false
61
+ client.subscribe(@destination) do |msg|
62
+ got_message = true if msg.body == body
63
+ client.ack(msg)
64
+ end
65
+ let_background_processor_run
66
+ client.disconnect
67
+
68
+ assert got_message
69
+ end
70
+
71
+ it "(#{adapter}) automatically publishes responses to the reply-to destination" do
72
+ client = Klomp::Client.new(@uris, :adapter => adapter).connect
73
+ reply_to_body = { 'reply_to_body' => rand(36**128).to_s(36) }
74
+
75
+ client.send(@destination, nil, { 'reply-to' => @destination })
76
+
77
+ got_message = false
78
+ client.subscribe(@destination) do |msg|
79
+ got_message = true if msg.body == reply_to_body
80
+ reply_to_body
81
+ end
82
+ let_background_processor_run
83
+ client.disconnect
84
+
85
+ assert got_message
86
+ end
87
+
88
+ it "(#{adapter}) sends messages with uuids in the 'id' header" do
89
+ client = Klomp::Client.new(@uris, :adapter => adapter, :translate_json => false).connect
90
+ client.send(@destination, '')
91
+
92
+ received_message = false
93
+ client.subscribe(@destination) do |msg|
94
+ received_message = msg
95
+ end
96
+ let_background_processor_run
97
+ client.disconnect
98
+
99
+ assert received_message
100
+ assert received_message.headers['id'], "message did not have an id"
101
+ assert received_message.headers['id'] =~ /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/
102
+ "message id did not look like a uuid"
103
+ end
104
+
105
+ it "(#{adapter}) allows customization of the uuid generator" do
106
+ generator = Object.new
107
+ def generator.generate; "42"; end
108
+
109
+ client = Klomp::Client.new(@uris, :adapter => adapter, :translate_json => false, :uuid => generator).connect
110
+ client.send(@destination, '')
111
+
112
+ received_message = false
113
+ client.subscribe(@destination) do |msg|
114
+ received_message = msg
115
+ end
116
+ let_background_processor_run
117
+ client.disconnect
118
+
119
+ assert received_message
120
+ assert received_message.headers['id'], "message did not have an id"
121
+ assert_equal "42", received_message.headers['id']
122
+ end
123
+
124
+ it "(#{adapter}) allows disabling generated message ids" do
125
+ client = Klomp::Client.new(@uris, :adapter => adapter, :translate_json => false, :uuid => false).connect
126
+ client.send(@destination, '')
127
+
128
+ received_message = false
129
+ client.subscribe(@destination) do |msg|
130
+ received_message = msg
131
+ end
132
+ let_background_processor_run
133
+ client.disconnect
134
+
135
+ assert received_message
136
+ refute received_message.headers['id'], "message had an id"
137
+ end
138
+
139
+ it "(#{adapter}) logs message ids" do
140
+ logger = Object.new
141
+ def logger.msgs; @msgs; end
142
+ def logger.debug(msg) (@msgs ||= []) << msg end
143
+
144
+ client = Klomp::Client.new(@uris, :adapter => adapter, :translate_json => false, :logger => logger).connect
145
+ client.send(@destination, '')
146
+
147
+ received_message = false
148
+ client.subscribe(@destination) do |msg|
149
+ received_message = msg
150
+ end
151
+ let_background_processor_run
152
+ client.disconnect
153
+
154
+ assert received_message
155
+ assert received_message.headers['id'], "message did not have an id"
156
+
157
+ assert_equal 2, logger.msgs.length
158
+ assert logger.msgs[0] =~ /\[Sending\] ID=([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/
159
+ sent_id = $1
160
+ assert logger.msgs[1] =~ /\[Received\] ID=([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/
161
+ received_id = $1
162
+ assert_equal sent_id, received_id
163
+ end
164
+
165
+ end
166
+
167
+ end
@@ -0,0 +1,81 @@
1
+ require 'minitest/autorun'
2
+ require 'minitest/pride'
3
+
4
+ require 'klomp'
5
+ require File.expand_path('../test_helper', __FILE__)
6
+
7
+ describe Klomp::Client do
8
+
9
+ include KlompTestHelpers
10
+
11
+ before do
12
+ @adapter = :onstomp
13
+ @uris = [
14
+ 'stomp://admin:password@localhost:61613',
15
+ 'stomp://admin:password@127.0.0.1:62613'
16
+ ]
17
+ @destination = '/queue/test_component.test_event'
18
+ end
19
+
20
+ it "raises an error if authentication fails" do
21
+ assert_raises OnStomp::ConnectFailedError do
22
+ Klomp::Client.new(@uris.first.sub('password', 'psswrd'), :adapter => @adapter).connect
23
+ end
24
+ end
25
+
26
+ it "sends heartbeat" do
27
+ client = Klomp::Client.new(@uris, :adapter => @adapter).connect
28
+ client.beat
29
+ client.disconnect
30
+ end
31
+
32
+ it "unsubscribes" do
33
+ client = Klomp::Client.new(@uris, :adapter => @adapter).connect
34
+
35
+ subscribe_frames = client.subscribe(@destination) { |msg| }
36
+ unsub_frames = client.unsubscribe(subscribe_frames)
37
+ let_background_processor_run
38
+ client.disconnect
39
+
40
+ assert_equal subscribe_frames.length, unsub_frames.length
41
+ assert client.subscriptions.flatten.empty?, "expected connection to have no subscriptions"
42
+ end
43
+
44
+ it 'uses a fibonacci back-off approach to reconnect' do
45
+ good_client = Object.new
46
+ def good_client.connect; true; end
47
+ def good_client.connected?; true; end
48
+ def good_client.connection; true; end
49
+
50
+ bad_client = Object.new
51
+ def bad_client.connect; raise "could not connect"; end
52
+ def bad_client.connected?; false; end
53
+
54
+ test_context = self
55
+ attempts = 0
56
+ conn = nil
57
+ fib = lambda {|n| (1..n).inject([0, 1]) {|fib,_| [fib[1], fib[0]+fib[1]]}.first}
58
+
59
+ pool_class = Class.new do
60
+ def initialize(*) end
61
+ def each(&blk) end
62
+ define_method :next_client do
63
+ attempts += 1
64
+ test_context.assert_equal fib[attempts], conn.retry_delay
65
+ if attempts == 6
66
+ good_client
67
+ else
68
+ bad_client
69
+ end
70
+ end
71
+ end
72
+
73
+ client = Klomp::Client.new(@uris.first, :adapter => @adapter, :pool => pool_class)
74
+ conn = client.write_conn
75
+ def conn.sleep_for_retry(*) end # skip sleep between retries for test
76
+
77
+ client.reconnect
78
+ assert_equal 6, attempts
79
+ end
80
+
81
+ end
@@ -0,0 +1,27 @@
1
+ require 'minitest/autorun'
2
+ require 'minitest/pride'
3
+
4
+ require 'klomp'
5
+ require File.expand_path('../test_helper', __FILE__)
6
+
7
+ describe Klomp::Client do
8
+
9
+ include KlompTestHelpers
10
+
11
+ before do
12
+ @adapter = :stomp
13
+ @uris = [
14
+ 'stomp://admin:password@localhost:61613',
15
+ 'stomp://admin:password@127.0.0.1:62613'
16
+ ]
17
+ @destination = '/queue/test_component.test_event'
18
+ end
19
+
20
+ it "unsubscribes" do
21
+ client = Klomp::Client.new(@uris, :adapter => @adapter).connect
22
+ client.subscribe(@destination) { |msg| }
23
+ client.unsubscribe(@destination)
24
+ client.disconnect
25
+ end
26
+
27
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: klomp
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 0.0.5
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-06-22 00:00:00.000000000 Z
12
+ date: 2012-08-10 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: onstomp
@@ -78,7 +78,9 @@ files:
78
78
  - lib/klomp.rb
79
79
  - lib/klomp/client.rb
80
80
  - tasks/test_failover.rake
81
- - test/test_client.rb
81
+ - test/test_client_all.rb
82
+ - test/test_client_onstomp.rb
83
+ - test/test_client_stomp.rb
82
84
  - test/test_helper.rb
83
85
  homepage: https://github.com/livingsocial/klomp
84
86
  licenses: []
@@ -94,7 +96,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
94
96
  version: '0'
95
97
  segments:
96
98
  - 0
97
- hash: 395786091883431087
99
+ hash: -1938950018895640841
98
100
  required_rubygems_version: !ruby/object:Gem::Requirement
99
101
  none: false
100
102
  requirements:
@@ -103,10 +105,12 @@ required_rubygems_version: !ruby/object:Gem::Requirement
103
105
  version: '0'
104
106
  requirements: []
105
107
  rubyforge_project:
106
- rubygems_version: 1.8.21
108
+ rubygems_version: 1.8.24
107
109
  signing_key:
108
110
  specification_version: 3
109
111
  summary: A simple wrapper around the OnStomp library with additional features
110
112
  test_files:
111
- - test/test_client.rb
113
+ - test/test_client_all.rb
114
+ - test/test_client_onstomp.rb
115
+ - test/test_client_stomp.rb
112
116
  - test/test_helper.rb
@@ -1,232 +0,0 @@
1
- require 'minitest/autorun'
2
- require 'minitest/pride'
3
-
4
- require 'klomp'
5
- require File.expand_path('../test_helper', __FILE__)
6
-
7
- describe Klomp::Client do
8
-
9
- include KlompTestHelpers
10
-
11
- before do
12
- @uris = [
13
- 'stomp://admin:password@localhost:61613',
14
- 'stomp://admin:password@127.0.0.1:62613'
15
- ]
16
- @destination = '/queue/test_component.test_event'
17
- end
18
-
19
- it 'accepts a single uri and establishes separate failover connections for writes and reads' do
20
- client = Klomp::Client.new(@uris.first).connect
21
-
22
- assert_equal [client.write_conn], client.read_conn
23
- assert client.write_conn.connected?
24
-
25
- client.disconnect
26
- end
27
-
28
- it 'accepts an array of uris and establishes separate failover connections for writes and reads' do
29
- client = Klomp::Client.new(@uris).connect
30
-
31
- assert client.write_conn.connected?
32
- refute_empty client.read_conn
33
- client.read_conn.each do |obj|
34
- assert obj.connected?
35
- end
36
-
37
- client.disconnect
38
- end
39
-
40
- it 'raises an error if authentication fails' do
41
- assert_raises OnStomp::ConnectFailedError do
42
- Klomp::Client.new(@uris.first.sub('password', 'psswrd')).connect
43
- end
44
- end
45
-
46
- it 'disconnnects' do
47
- client = Klomp::Client.new(@uris.first).connect
48
- assert client.write_conn.connected?
49
- client.disconnect
50
- refute client.write_conn.connected?
51
- end
52
-
53
- it 'has a logger' do
54
- logger = Logger.new(STDOUT)
55
- client = Klomp::Client.new(@uris, :logger=>logger)
56
- assert_equal client.log, logger
57
- end
58
-
59
- it 'sends heartbeat' do
60
- client = Klomp::Client.new(@uris).connect
61
- client.beat
62
- client.disconnect
63
- end
64
-
65
- it 'sends requests and gets responses' do
66
- client = Klomp::Client.new(@uris).connect
67
- body = { 'body' => rand(36**128).to_s(36) }
68
-
69
- client.send(@destination, body, :ack=>'client')
70
-
71
- got_message = false
72
- client.subscribe(@destination) do |msg|
73
- got_message = true if msg.body == body
74
- client.ack(msg)
75
- end
76
- let_background_processor_run
77
- assert got_message
78
-
79
- client.disconnect
80
- end
81
-
82
- it 'automatically publishes responses to the reply-to destination' do
83
- client = Klomp::Client.new(@uris).connect
84
- reply_to_body = { 'reply_to_body' => rand(36**128).to_s(36) }
85
-
86
- client.send(@destination, nil, { 'reply-to' => @destination })
87
-
88
- got_message = false
89
- client.subscribe(@destination) do |msg|
90
- got_message = true if msg.body == reply_to_body
91
- reply_to_body
92
- end
93
- let_background_processor_run
94
- assert got_message
95
-
96
- client.disconnect
97
- end
98
-
99
- it 'unsubscribes' do
100
- client = Klomp::Client.new(@uris).connect
101
-
102
- subscribe_frames = client.subscribe(@destination) { |msg| }
103
- unsub_frames = client.unsubscribe(subscribe_frames)
104
- assert_equal subscribe_frames.length, unsub_frames.length
105
- let_background_processor_run
106
-
107
- assert client.subscriptions.flatten.empty?, "expected connection to have no subscriptions"
108
-
109
- client.disconnect
110
- end
111
-
112
- it 'sends all unknown options through to OnStomp' do
113
- client = Klomp::Client.new(@uris.first, :haz_cheezburgers => true, :retry_attempts => 42).connect
114
- assert client.write_conn.connected?
115
- assert_equal 42, client.write_conn.retry_attempts
116
- client.disconnect
117
- end
118
-
119
- it 'uses a fibonacci back-off approach to reconnect' do
120
- good_client = Object.new
121
- def good_client.connect; true; end
122
- def good_client.connected?; true; end
123
- def good_client.connection; true; end
124
-
125
- bad_client = Object.new
126
- def bad_client.connect; raise "could not connect"; end
127
- def bad_client.connected?; false; end
128
-
129
- test_context = self
130
- attempts = 0
131
- conn = nil
132
- fib = lambda {|n| (1..n).inject([0, 1]) {|fib,_| [fib[1], fib[0]+fib[1]]}.first}
133
-
134
- pool_class = Class.new do
135
- def initialize(*) end
136
- def each(&blk) end
137
- define_method :next_client do
138
- attempts += 1
139
- test_context.assert_equal fib[attempts], conn.retry_delay
140
- if attempts == 6
141
- good_client
142
- else
143
- bad_client
144
- end
145
- end
146
- end
147
-
148
- client = Klomp::Client.new(@uris.first, :pool => pool_class)
149
- conn = client.write_conn
150
- def conn.sleep_for_retry(*) end # skip sleep between retries for test
151
-
152
- client.reconnect
153
- assert_equal 6, attempts
154
- end
155
-
156
- it 'sends messages with uuids in the :id header' do
157
- client = Klomp::Client.new(@uris, :translate_json => false).connect
158
- client.send(@destination, '')
159
-
160
- received_message = false
161
- client.subscribe(@destination) do |msg|
162
- received_message = msg
163
- end
164
- let_background_processor_run
165
- assert received_message
166
- assert received_message[:id], "message did not have an id"
167
- assert received_message[:id] =~ /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/
168
- "message id did not look like a uuid"
169
-
170
- client.disconnect
171
- end
172
-
173
- it 'allows customization of the uuid generator' do
174
- generator = Object.new
175
- def generator.generate; "42"; end
176
-
177
- client = Klomp::Client.new(@uris, :translate_json => false, :uuid => generator).connect
178
- client.send(@destination, '')
179
-
180
- received_message = false
181
- client.subscribe(@destination) do |msg|
182
- received_message = msg
183
- end
184
- let_background_processor_run
185
- assert received_message
186
- assert received_message[:id], "message did not have an id"
187
- assert_equal "42", received_message[:id]
188
-
189
- client.disconnect
190
- end
191
-
192
- it 'allows disabling generated message ids' do
193
- client = Klomp::Client.new(@uris, :translate_json => false, :uuid => false).connect
194
- client.send(@destination, '')
195
-
196
- received_message = false
197
- client.subscribe(@destination) do |msg|
198
- received_message = msg
199
- end
200
- let_background_processor_run
201
- assert received_message
202
- refute received_message[:id], "message had an id"
203
-
204
- client.disconnect
205
- end
206
-
207
- it 'logs message ids' do
208
- logger = Object.new
209
- def logger.msgs; @msgs; end
210
- def logger.info(msg) (@msgs ||= []) << msg end
211
-
212
- client = Klomp::Client.new(@uris, :translate_json => false, :logger => logger).connect
213
- client.send(@destination, '')
214
-
215
- received_message = false
216
- client.subscribe(@destination) do |msg|
217
- received_message = msg
218
- end
219
- let_background_processor_run
220
- assert received_message
221
- assert received_message[:id], "message did not have an id"
222
-
223
- assert_equal 2, logger.msgs.length
224
- assert logger.msgs[0] =~ /\[Sending\] ID=([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/
225
- sent_id = $1
226
- assert logger.msgs[1] =~ /\[Received\] ID=([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/
227
- received_id = $1
228
- assert_equal sent_id, received_id
229
-
230
- client.disconnect
231
- end
232
- end