klomp 0.0.4 → 0.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|