klomp 0.0.4 → 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- data/ChangeLog.md +5 -0
- data/Gemfile +1 -0
- data/Gemfile.lock +2 -0
- data/README.md +20 -18
- data/lib/klomp.rb +1 -1
- data/lib/klomp/client.rb +133 -54
- data/tasks/test_failover.rake +2 -2
- data/test/test_client_all.rb +167 -0
- data/test/test_client_onstomp.rb +81 -0
- data/test/test_client_stomp.rb +27 -0
- metadata +10 -6
- data/test/test_client.rb +0 -232
data/ChangeLog.md
CHANGED
@@ -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
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
|
-
|
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
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
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
|
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
|
36
|
-
returns a boolean value, Klomp's `connected?` will return
|
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
|
-
|
79
|
-
|
80
|
-
|
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
|
-
|
106
|
-
|
107
|
-
* Initial release
|
109
|
+
See the [ChangeLog file](/livingsocial/klomp/blob/master/ChangeLog.md).
|
108
110
|
|
109
111
|
## License
|
110
112
|
|
data/lib/klomp.rb
CHANGED
data/lib/klomp/client.rb
CHANGED
@@ -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
|
-
|
31
|
-
|
32
|
-
|
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
|
-
|
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
|
-
@
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
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
|
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[
|
130
|
+
headers['content-type'] = 'application/json'
|
62
131
|
else
|
63
132
|
body = body.to_s
|
64
133
|
end
|
65
|
-
uuid = headers[
|
66
|
-
log.
|
67
|
-
|
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.
|
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[
|
160
|
+
if @auto_reply_to && !msg.headers['reply-to'].nil?
|
85
161
|
if reply_args.is_a?(Array)
|
86
|
-
send(msg.headers[
|
162
|
+
send(msg.headers['reply-to'], *reply_args)
|
87
163
|
else
|
88
|
-
send(msg.headers[
|
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
|
-
|
98
|
-
|
99
|
-
|
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.
|
202
|
+
@read_conn.inject({}) { |memo,obj| memo.update({ obj => obj.__send__(method, *args, &block) }) }
|
129
203
|
else
|
130
|
-
@all_conn.
|
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
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
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
|
-
|
149
|
-
|
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
|
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
|
-
]).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
|
+
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-
|
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/
|
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:
|
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.
|
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/
|
113
|
+
- test/test_client_all.rb
|
114
|
+
- test/test_client_onstomp.rb
|
115
|
+
- test/test_client_stomp.rb
|
112
116
|
- test/test_helper.rb
|
data/test/test_client.rb
DELETED
@@ -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
|