klomp 0.0.6 → 0.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/ChangeLog.md +5 -0
- data/Gemfile +0 -1
- data/Gemfile.lock +0 -2
- data/README.md +15 -19
- data/lib/klomp.rb +1 -1
- data/lib/klomp/client.rb +53 -133
- data/tasks/test_failover.rake +2 -2
- data/test/test_client.rb +232 -0
- metadata +5 -9
- data/test/test_client_all.rb +0 -167
- data/test/test_client_onstomp.rb +0 -81
- data/test/test_client_stomp.rb +0 -27
data/ChangeLog.md
CHANGED
@@ -1,6 +1,11 @@
|
|
1
1
|
Changes
|
2
2
|
--------------------------------------------------------------------------------
|
3
3
|
|
4
|
+
0.0.7 (2012/8/15)
|
5
|
+
================================================================================
|
6
|
+
|
7
|
+
- back out the stomp/onstomp adapter code. we'll revisit this later
|
8
|
+
|
4
9
|
0.0.6 (2012/8/10)
|
5
10
|
================================================================================
|
6
11
|
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -1,16 +1,16 @@
|
|
1
1
|
# Klomp
|
2
2
|
|
3
|
-
Klomp is a simple wrapper around the [
|
4
|
-
|
5
|
-
additional HA and usability features:
|
3
|
+
Klomp is a simple wrapper around the [OnStomp](https://github.com/meadvillerb/onstomp/)
|
4
|
+
library with some additional HA and usability features:
|
6
5
|
|
7
6
|
* When initialized with multiple broker URIs, Klomp will publish messages to
|
8
7
|
one broker at a time, but will consume from all brokers simultaneously. This is
|
9
|
-
a slight improvement over
|
10
|
-
and subscribing through a single "active" broker.
|
11
|
-
technique can lead to a split-brain
|
12
|
-
received by a subset of your STOMP clients.
|
13
|
-
simultaneously, Klomp ensures that no message 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.
|
14
14
|
|
15
15
|
* Where applicable, message bodies are automatically translated between native
|
16
16
|
Ruby and JSON objects.
|
@@ -26,13 +26,14 @@ 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
|
-
|
29
|
+
OnStomp API (see [OnStomp's UserNarrative](https://github.com/meadvillerb/onstomp/blob/master/extra_doc/UserNarrative.md))
|
30
|
+
via a `Klomp::Client`:
|
30
31
|
|
31
32
|
client = Klomp::Client.new([ ... ])
|
32
33
|
|
33
34
|
However, there will be some differences in the API due to how `Klomp::Client`
|
34
|
-
manages connections. For example, while
|
35
|
-
returns a
|
35
|
+
manages connections. For example, while the `connected?` method normally
|
36
|
+
returns a boolean value, Klomp's `connected?` will return an array of booleans
|
36
37
|
(i.e. one result for each broker).
|
37
38
|
|
38
39
|
### Fibonacci back-off retry behavior
|
@@ -58,11 +59,6 @@ pass implements a `#generate` method that returns a string ID.
|
|
58
59
|
<th>Default value</th>
|
59
60
|
<th>Description</th>
|
60
61
|
</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>
|
66
62
|
<tr>
|
67
63
|
<td>:translate_json</td>
|
68
64
|
<td>true</td>
|
@@ -79,9 +75,9 @@ pass implements a `#generate` method that returns a string ID.
|
|
79
75
|
<td>Logger object</td>
|
80
76
|
</tr>
|
81
77
|
<tr>
|
82
|
-
|
83
|
-
|
84
|
-
|
78
|
+
<td>:uuid</td>
|
79
|
+
<td>UUID.new</td>
|
80
|
+
<td>UUID generator object, responds to :generate and returns an ID</td>
|
85
81
|
</tr>
|
86
82
|
</table>
|
87
83
|
|
data/lib/klomp.rb
CHANGED
data/lib/klomp/client.rb
CHANGED
@@ -3,47 +3,19 @@ require 'onstomp/failover'
|
|
3
3
|
require 'json'
|
4
4
|
require 'uuid'
|
5
5
|
require 'logger'
|
6
|
-
require 'pp'
|
7
6
|
|
8
7
|
class OnStomp::Failover::Client
|
9
8
|
# Save previous N, N-1 delays for fibonacci backoff
|
10
9
|
attr_accessor :prev_retry_delay
|
11
10
|
end
|
12
11
|
|
13
|
-
class Hash
|
14
|
-
def to_query_string
|
15
|
-
self.inject([]) { |memo,(k,v)| memo + ["#{k}=#{v}"] }.join('&')
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
12
|
module Klomp
|
20
13
|
|
21
14
|
class Client
|
22
|
-
attr_reader :read_conn, :write_conn
|
15
|
+
attr_reader :read_conn, :write_conn
|
23
16
|
attr_accessor :last_connect_exception
|
24
17
|
|
25
|
-
WRITE_ONLY_METHODS = [
|
26
|
-
:abort,
|
27
|
-
:begin,
|
28
|
-
:commit,
|
29
|
-
]
|
30
|
-
|
31
|
-
READ_ONLY_METHODS = [
|
32
|
-
:ack,
|
33
|
-
:nack,
|
34
|
-
]
|
35
|
-
|
36
|
-
TRANSLATE_METHODS = {
|
37
|
-
:connected? => {
|
38
|
-
:stomp => :open?
|
39
|
-
},
|
40
|
-
:ack => {
|
41
|
-
:stomp => :acknowledge
|
42
|
-
}
|
43
|
-
}
|
44
|
-
|
45
18
|
def initialize(uri, options={})
|
46
|
-
@adapter = options.fetch(:adapter, :onstomp)
|
47
19
|
@translate_json = options.fetch(:translate_json, true)
|
48
20
|
@auto_reply_to = options.fetch(:auto_reply_to, true)
|
49
21
|
@logger = options.fetch(:logger, nil)
|
@@ -55,93 +27,44 @@ module Klomp
|
|
55
27
|
options[:retry_delay] ||= 1
|
56
28
|
options[:retry_attempts] ||= -1
|
57
29
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
@write_conn = OnStomp::Failover::Client.new(uri, options)
|
62
|
-
@read_conn = uri.map {|obj| OnStomp::Failover::Client.new([obj], options) }
|
63
|
-
else
|
64
|
-
@write_conn = OnStomp::Failover::Client.new([uri], options)
|
65
|
-
@read_conn = [@write_conn]
|
66
|
-
end
|
67
|
-
when :stomp
|
68
|
-
require 'stomp'
|
69
|
-
|
70
|
-
# Failover in the Stomp library is kind of flaky. If the client
|
71
|
-
# temporarily loses its connection, it is eventually able to reconnect
|
72
|
-
# and resume sending messages. However, the subscribe thread never
|
73
|
-
# seems to recover. One workaround I discovered is to always create new
|
74
|
-
# clients with the following URL scheme:
|
75
|
-
#
|
76
|
-
# failover:(stomp://localhost,stomp://localhost)
|
77
|
-
#
|
78
|
-
# Notice that we're using a failover URL in which the same host is
|
79
|
-
# specified twice. It's a pretty silly hack, but it's the only way I've
|
80
|
-
# been able to get failover to work reliably.
|
81
|
-
#
|
82
|
-
# - Mike Conigliaro
|
83
|
-
#
|
84
|
-
if uri.is_a?(Array)
|
85
|
-
@write_conn = Stomp::Client.new("failover:(#{uri.join(',')})?#{options.to_query_string}")
|
86
|
-
@read_conn = uri.map {|obj| Stomp::Client.new("failover:(#{obj},#{obj})?#{options.to_query_string}") }
|
87
|
-
else
|
88
|
-
@write_conn = Stomp::Client.new("failover:(#{uri},#{uri})?#{options.to_query_string}")
|
89
|
-
@read_conn = [@write_conn]
|
90
|
-
end
|
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) }
|
91
33
|
else
|
92
|
-
|
34
|
+
@write_conn = OnStomp::Failover::Client.new([uri], options)
|
35
|
+
@read_conn = [@write_conn]
|
93
36
|
end
|
94
37
|
@all_conn = ([@write_conn] + @read_conn).uniq
|
95
38
|
configure_connections
|
96
39
|
end
|
97
40
|
|
98
41
|
def connect
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
ensure
|
111
|
-
conn.retry_attempts = attempts
|
112
|
-
end
|
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
|
113
53
|
end
|
114
54
|
end
|
115
55
|
self
|
116
56
|
end
|
117
57
|
|
118
|
-
def
|
119
|
-
method = case @adapter
|
120
|
-
when :onstomp
|
121
|
-
:disconnect
|
122
|
-
when :stomp
|
123
|
-
:close
|
124
|
-
end
|
125
|
-
@all_conn.inject({}) { |memo,obj| memo.update({ obj => obj.__send__(method) }) }
|
126
|
-
end
|
127
|
-
|
128
|
-
def send(dest, body, headers={}, &block)
|
58
|
+
def send(dest, body, headers={}, &cb)
|
129
59
|
if @translate_json && body.respond_to?(:to_json)
|
130
60
|
body = body.to_json
|
131
|
-
headers['content-type'] = 'application/json'
|
61
|
+
headers[:'content-type'] = 'application/json'
|
132
62
|
else
|
133
63
|
body = body.to_s
|
134
64
|
end
|
135
|
-
uuid = headers[
|
65
|
+
uuid = headers[:id] = @uuid.generate if @uuid
|
136
66
|
log.debug("[Sending] ID=#{uuid} Destination=#{dest} Body=#{body.inspect} Headers=#{headers.inspect}") if log
|
137
|
-
|
138
|
-
method = case @adapter
|
139
|
-
when :onstomp
|
140
|
-
:send
|
141
|
-
when :stomp
|
142
|
-
:publish
|
143
|
-
end
|
144
|
-
@write_conn.__send__(method, dest, body, headers, &block)
|
67
|
+
@write_conn.send(dest, body, headers, &cb)
|
145
68
|
end
|
146
69
|
alias publish send
|
147
70
|
|
@@ -149,7 +72,7 @@ module Klomp
|
|
149
72
|
frames = []
|
150
73
|
@read_conn.each do |c|
|
151
74
|
frames << c.subscribe(*args) do |msg|
|
152
|
-
log.debug("[Received] ID=#{msg
|
75
|
+
log.debug("[Received] ID=#{msg[:id]} Body=#{msg.body.inspect} Headers=#{msg.headers.to_hash.inspect}") if log
|
153
76
|
if @translate_json
|
154
77
|
msg.body = begin
|
155
78
|
JSON.parse(msg.body)
|
@@ -158,11 +81,11 @@ module Klomp
|
|
158
81
|
end
|
159
82
|
end
|
160
83
|
reply_args = yield msg
|
161
|
-
if @auto_reply_to && !msg.headers['reply-to'].nil?
|
84
|
+
if @auto_reply_to && !msg.headers[:'reply-to'].nil?
|
162
85
|
if reply_args.is_a?(Array)
|
163
|
-
send(msg.headers['reply-to'], *reply_args)
|
86
|
+
send(msg.headers[:'reply-to'], *reply_args)
|
164
87
|
else
|
165
|
-
send(msg.headers['reply-to'], reply_args)
|
88
|
+
send(msg.headers[:'reply-to'], reply_args)
|
166
89
|
end
|
167
90
|
end
|
168
91
|
end
|
@@ -171,16 +94,11 @@ module Klomp
|
|
171
94
|
end
|
172
95
|
|
173
96
|
def unsubscribe(frames, headers={})
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
raise ArgumentError,
|
178
|
-
"frames is not an array or its length does not match number of connections"
|
179
|
-
end
|
180
|
-
frames.each_with_index.map {|f,i| @read_conn[i].unsubscribe f, headers }
|
181
|
-
else
|
182
|
-
@read_conn.each { |obj| obj.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"
|
183
100
|
end
|
101
|
+
frames.each_with_index.map {|f,i| @read_conn[i].unsubscribe f, headers }
|
184
102
|
end
|
185
103
|
|
186
104
|
def subscriptions
|
@@ -191,45 +109,47 @@ module Klomp
|
|
191
109
|
@logger
|
192
110
|
end
|
193
111
|
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
112
|
+
WRITE_ONLY_METHODS = [
|
113
|
+
:abort,
|
114
|
+
:begin,
|
115
|
+
:commit,
|
116
|
+
]
|
198
117
|
|
118
|
+
READ_ONLY_METHODS = [
|
119
|
+
:ack,
|
120
|
+
:nack,
|
121
|
+
]
|
122
|
+
|
123
|
+
def method_missing(method, *args, &block)
|
199
124
|
case method
|
200
125
|
when *WRITE_ONLY_METHODS
|
201
126
|
@write_conn.__send__(method, *args, &block)
|
202
127
|
when *READ_ONLY_METHODS
|
203
|
-
@read_conn.
|
128
|
+
@read_conn.map {|c| c.__send__(method, *args, &block) }
|
204
129
|
else
|
205
|
-
@all_conn.
|
130
|
+
@all_conn.map {|c| c.__send__(method, *args) }
|
206
131
|
end
|
207
132
|
end
|
208
133
|
|
209
134
|
private
|
210
|
-
|
211
135
|
def configure_connections
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
else
|
221
|
-
conn.prev_retry_delay, conn.retry_delay = conn.retry_delay, conn.prev_retry_delay + conn.retry_delay
|
222
|
-
end
|
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
|
223
144
|
end
|
224
145
|
end
|
146
|
+
end
|
225
147
|
|
226
|
-
|
227
|
-
|
228
|
-
end
|
148
|
+
c.on_failover_connect_failure do
|
149
|
+
klomp_client.last_connect_exception = $!
|
229
150
|
end
|
230
151
|
end
|
231
152
|
end
|
232
|
-
|
233
153
|
end
|
234
154
|
|
235
155
|
end
|
data/tasks/test_failover.rake
CHANGED
@@ -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
|
-
]
|
13
|
+
]).connect
|
14
14
|
|
15
15
|
last_i = nil
|
16
16
|
client.subscribe("/queue/test") do |msg|
|
17
17
|
print "-"
|
18
|
-
last_i = msg.body.
|
18
|
+
last_i = msg.body.to_i
|
19
19
|
end
|
20
20
|
|
21
21
|
begin
|
data/test/test_client.rb
ADDED
@@ -0,0 +1,232 @@
|
|
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.debug(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
|
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
|
+
version: 0.0.7
|
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-08-
|
12
|
+
date: 2012-08-15 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: onstomp
|
@@ -78,9 +78,7 @@ files:
|
|
78
78
|
- lib/klomp.rb
|
79
79
|
- lib/klomp/client.rb
|
80
80
|
- tasks/test_failover.rake
|
81
|
-
- test/
|
82
|
-
- test/test_client_onstomp.rb
|
83
|
-
- test/test_client_stomp.rb
|
81
|
+
- test/test_client.rb
|
84
82
|
- test/test_helper.rb
|
85
83
|
homepage: https://github.com/livingsocial/klomp
|
86
84
|
licenses: []
|
@@ -96,7 +94,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
96
94
|
version: '0'
|
97
95
|
segments:
|
98
96
|
- 0
|
99
|
-
hash:
|
97
|
+
hash: 1929971310502642728
|
100
98
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
101
99
|
none: false
|
102
100
|
requirements:
|
@@ -110,7 +108,5 @@ signing_key:
|
|
110
108
|
specification_version: 3
|
111
109
|
summary: A simple wrapper around the OnStomp library with additional features
|
112
110
|
test_files:
|
113
|
-
- test/
|
114
|
-
- test/test_client_onstomp.rb
|
115
|
-
- test/test_client_stomp.rb
|
111
|
+
- test/test_client.rb
|
116
112
|
- test/test_helper.rb
|
data/test/test_client_all.rb
DELETED
@@ -1,167 +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
|
-
[ :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
|
data/test/test_client_onstomp.rb
DELETED
@@ -1,81 +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
|
-
@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
|
data/test/test_client_stomp.rb
DELETED
@@ -1,27 +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
|
-
@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
|