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