stomp 1.1 → 1.1.3

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 CHANGED
@@ -1,3 +1,9 @@
1
+ == 1.1.3 2009-24-11
2
+
3
+ * Failover support
4
+ * SSL support
5
+ * Stomp::Connection and Stomp::Client accept a hash on their constructor
6
+
1
7
  == 1.1 2009-27-02
2
8
 
3
9
  * Ruby 1.9 Support
data/README.rdoc CHANGED
@@ -15,6 +15,45 @@ An implementation of the Stomp protocol (http://stomp.codehaus.org/Protocol) for
15
15
  p msg
16
16
  end
17
17
 
18
+ ===Failover + SSL Example URL Usage
19
+
20
+ options = "initialReconnectDelay=5000&randomize=false&useExponentialBackOff=false"
21
+
22
+ #remotehost1 uses SSL, remotehost2 doesn't
23
+ client = Stomp::Client.new("failover:(stomp+ssl://login1:passcode1@remotehost1:61612,stomp://login2:passcode2@remotehost2:61613)?#{options}")
24
+
25
+ client.send("/my/queue", "hello world!")
26
+ client.subscribe("/my/queue") do |msg|
27
+ p msg
28
+ end
29
+
30
+ ===Hash Example Usage
31
+
32
+ hash = {
33
+ :hosts => [
34
+ {:login => "login1", :passcode => "passcode1", :host => "remotehost1", :port => 61612, :ssl => true},
35
+ {:login => "login2", :passcode => "passcode2", :host => "remotehost2", :port => 61613, :ssl => false},
36
+
37
+ ],
38
+ # These are the default parameters, don't need to be set
39
+ :initial_reconnect_delay => 0.01,
40
+ :max_reconnect_delay => 30.0,
41
+ :use_exponential_back_off => true,
42
+ :back_off_multiplier => 2,
43
+ :max_reconnect_attempts => 0,
44
+ :randomize => false,
45
+ :backup => false,
46
+ :timeout => -1,
47
+ :connect_headers => {}
48
+ }
49
+
50
+ # for client
51
+ client = Stomp::Client.new(hash)
52
+
53
+ # for connection
54
+ connection = Stomp::Connection.new(hash)
55
+
56
+
18
57
  ===Contact info
19
58
 
20
59
  Up until March 2009 the project was maintained and primarily developed by Brian McCallister.
data/lib/stomp/client.rb CHANGED
@@ -7,7 +7,8 @@ module Stomp
7
7
  # in that thread if you have much message volume.
8
8
  class Client
9
9
 
10
- attr_reader :login, :passcode, :host, :port, :reliable, :running
10
+ attr_reader :login, :passcode, :host, :port, :reliable, :running, :parameters
11
+ alias :obj_send :send
11
12
 
12
13
  # A new Client object can be initialized using two forms:
13
14
  #
@@ -30,22 +31,46 @@ module Stomp
30
31
  #
31
32
  def initialize(login = '', passcode = '', host = 'localhost', port = 61613, reliable = false)
32
33
 
33
- # Parse stomp:// URL's or set positional params
34
- case login
35
- when /stomp:\/\/([\w\.]+):(\d+)/ # e.g. stomp://host:port
36
- # grabs the matching positions out of the regex which are stored as
37
- # $1 (host), $2 (port), etc
38
- @login = ''
39
- @passcode = ''
40
- @host = $1
41
- @port = $2.to_i
42
- @reliable = false
43
- when /stomp:\/\/([\w\.]+):(\w+)@([\w\.]+):(\d+)/ # e.g. stomp://login:passcode@host:port
44
- @login = $1
45
- @passcode = $2
46
- @host = $3
47
- @port = $4.to_i
34
+ # Parse stomp:// URL's or set params
35
+ if login.is_a?(Hash)
36
+ @parameters = login
37
+
38
+ first_host = @parameters[:hosts][0]
39
+
40
+ @login = first_host[:login]
41
+ @passcode = first_host[:passcode]
42
+ @host = first_host[:host]
43
+ @port = first_host[:port] || default_port(first_host[:ssl])
44
+
45
+ @reliable = true
46
+
47
+ elsif login =~ /^stomp:\/\/(([\w\.]+):(\w+)@)?([\w\.]+):(\d+)/ # e.g. stomp://login:passcode@host:port or stomp://host:port
48
+ @login = $2 || ""
49
+ @passcode = $3 || ""
50
+ @host = $4
51
+ @port = $5.to_i
48
52
  @reliable = false
53
+ elsif login =~ /^failover:(\/\/)?\(stomp(\+ssl)?:\/\/(([\w\.]*):(\w*)@)?([\w\.]+):(\d+)(,stomp(\+ssl)?:\/\/(([\w\.]*):(\w*)@)?([\w\.]+):(\d+)\))+(\?(.*))?$/ # e.g. failover://(stomp://login1:passcode1@localhost:61616,stomp://login2:passcode2@remotehost:61617)?option1=param
54
+
55
+ first_host = {}
56
+ first_host[:ssl] = !$2.nil?
57
+ @login = first_host[:login] = $4 || ""
58
+ @passcode = first_host[:passcode] = $5 || ""
59
+ @host = first_host[:host] = $6
60
+ @port = first_host[:port] = $7.to_i || default_port(first_host[:ssl])
61
+
62
+ options = $16 || ""
63
+ parts = options.split(/&|=/)
64
+ options = Hash[*parts]
65
+
66
+ hosts = [first_host] + parse_hosts(login)
67
+
68
+ @parameters = {}
69
+ @parameters[:hosts] = hosts
70
+
71
+ @parameters.merge! filter_options(options)
72
+
73
+ @reliable = true
49
74
  else
50
75
  @login = login
51
76
  @passcode = passcode
@@ -54,38 +79,21 @@ module Stomp
54
79
  @reliable = reliable
55
80
  end
56
81
 
57
- raise ArgumentError if @host.nil? || @host.empty?
58
- raise ArgumentError if @port.nil? || @port == '' || @port < 1 || @port > 65535
59
- raise ArgumentError unless @reliable.is_a?(TrueClass) || @reliable.is_a?(FalseClass)
82
+ check_arguments!
60
83
 
61
84
  @id_mutex = Mutex.new
62
85
  @ids = 1
63
- @connection = Connection.new(@login, @passcode, @host, @port, @reliable)
64
- @listeners = {}
65
- @receipt_listeners = {}
66
- @running = true
67
- @replay_messages_by_txn = {}
68
-
69
- @listener_thread = Thread.start do
70
- while @running
71
- message = @connection.receive
72
- case
73
- when message.nil?
74
- break
75
- when message.command == 'MESSAGE'
76
- if listener = @listeners[message.headers['destination']]
77
- listener.call(message)
78
- end
79
- when message.command == 'RECEIPT'
80
- if listener = @receipt_listeners[message.headers['receipt-id']]
81
- listener.call(message)
82
- end
83
- end
84
- end
86
+
87
+ if @parameters
88
+ @connection = Connection.new(@parameters)
89
+ else
90
+ @connection = Connection.new(@login, @passcode, @host, @port, @reliable)
85
91
  end
92
+
93
+ start_listeners
86
94
 
87
95
  end
88
-
96
+
89
97
  # Syntactic sugar for 'Client.new' See 'initialize' for usage.
90
98
  def self.open(login = '', passcode = '', host = 'localhost', port = 61613, reliable = false)
91
99
  Client.new(login, passcode, host, port, reliable)
@@ -201,7 +209,76 @@ module Stomp
201
209
  @receipt_listeners[id] = listener
202
210
  id
203
211
  end
212
+
213
+ def default_port(ssl)
214
+ return 61612 if ssl
215
+
216
+ 61613
217
+ end
218
+
219
+ def parse_hosts(url)
220
+ hosts = []
221
+
222
+ host_match = /stomp(\+ssl)?:\/\/(([\w\.]*):(\w*)@)?([\w\.]+):(\d+)\)/
223
+ url.scan(host_match).each do |match|
224
+ host = {}
225
+ host[:ssl] = !match[0].nil?
226
+ host[:login] = match[2] || ""
227
+ host[:passcode] = match[3] || ""
228
+ host[:host] = match[4]
229
+ host[:port] = match[5].to_i
230
+
231
+ hosts << host
232
+ end
233
+
234
+ hosts
235
+ end
236
+
237
+ def check_arguments!
238
+ raise ArgumentError if @host.nil? || @host.empty?
239
+ raise ArgumentError if @port.nil? || @port == '' || @port < 1 || @port > 65535
240
+ raise ArgumentError unless @reliable.is_a?(TrueClass) || @reliable.is_a?(FalseClass)
241
+ end
242
+
243
+ def filter_options(options)
244
+ new_options = {}
245
+ new_options[:initial_reconnect_delay] = (options["initialReconnectDelay"] || 10).to_f / 1000 # In ms
246
+ new_options[:max_reconnect_delay] = (options["maxReconnectDelay"] || 30000 ).to_f / 1000 # In ms
247
+ new_options[:use_exponential_back_off] = !(options["useExponentialBackOff"] == "false") # Default: true
248
+ new_options[:back_off_multiplier] = (options["backOffMultiplier"] || 2 ).to_i
249
+ new_options[:max_reconnect_attempts] = (options["maxReconnectAttempts"] || 0 ).to_i
250
+ new_options[:randomize] = options["randomize"] == "true" # Default: false
251
+ new_options[:backup] = false # Not implemented yet: I'm using a master X slave solution
252
+ new_options[:timeout] = -1 # Not implemented yet: a "timeout(5) do ... end" would do the trick, feel free
253
+
254
+ new_options
255
+ end
256
+
257
+ def start_listeners
258
+ @listeners = {}
259
+ @receipt_listeners = {}
260
+ @running = true
261
+ @replay_messages_by_txn = {}
204
262
 
263
+ @listener_thread = Thread.start do
264
+ while @running
265
+ message = @connection.receive
266
+ case
267
+ when message.nil?
268
+ break
269
+ when message.command == 'MESSAGE'
270
+ if listener = @listeners[message.headers['destination']]
271
+ listener.call(message)
272
+ end
273
+ when message.command == 'RECEIPT'
274
+ if listener = @receipt_listeners[message.headers['receipt-id']]
275
+ listener.call(message)
276
+ end
277
+ end
278
+ end
279
+ end
280
+
281
+ end
205
282
  end
206
283
  end
207
284
 
@@ -4,7 +4,7 @@ module Stomp
4
4
  # synchronous receives
5
5
  class Connection
6
6
 
7
-
7
+ alias :obj_send :send
8
8
  # A new Connection object accepts the following parameters:
9
9
  #
10
10
  # login (String, default : '')
@@ -14,9 +14,28 @@ module Stomp
14
14
  # reliable (Boolean, default : false)
15
15
  # reconnect_delay (Integer, default : 5)
16
16
  #
17
- # e.g. c = Client.new("username", "password", "localhost", 61613, true)
17
+ # e.g. c = Connection.new("username", "password", "localhost", 61613, true)
18
+ #
19
+ # Hash:
20
+ #
21
+ # hash = {
22
+ # :hosts => [
23
+ # {:login => "login1", :passcode => "passcode1", :host => "localhost", :port => 61616, :ssl => false},
24
+ # {:login => "login2", :passcode => "passcode2", :host => "remotehost", :port => 61617, :ssl => false}
25
+ # ],
26
+ # :initial_reconnect_delay => 0.01,
27
+ # :max_reconnect_delay => 30.0,
28
+ # :use_exponential_back_off => true,
29
+ # :back_off_multiplier => 2,
30
+ # :max_reconnect_attempts => 0,
31
+ # :randomize => false,
32
+ # :backup => false,
33
+ # :timeout => -1
34
+ # }
35
+ #
36
+ # e.g. c = Connection.new(hash)
18
37
  #
19
- # TODO
38
+ # TODO
20
39
  # Stomp URL :
21
40
  # A Stomp URL must begin with 'stomp://' and can be in one of the following forms:
22
41
  #
@@ -26,22 +45,42 @@ module Stomp
26
45
  # stomp://user:pass@host.domain.tld:port
27
46
  #
28
47
  def initialize(login = '', passcode = '', host = 'localhost', port = 61613, reliable = false, reconnect_delay = 5, connect_headers = {})
29
- @host = host
30
- @port = port
31
- @login = login
32
- @passcode = passcode
48
+ if login.is_a?(Hash)
49
+ hashed_initialize(login)
50
+ else
51
+ @host = host
52
+ @port = port
53
+ @login = login
54
+ @passcode = passcode
55
+ @reliable = reliable
56
+ @reconnect_delay = reconnect_delay
57
+ @connect_headers = connect_headers
58
+ @ssl = false
59
+ @parameters = nil
60
+ end
61
+
33
62
  @transmit_semaphore = Mutex.new
34
63
  @read_semaphore = Mutex.new
35
64
  @socket_semaphore = Mutex.new
36
- @reliable = reliable
37
- @reconnect_delay = reconnect_delay
38
- @connect_headers = connect_headers
39
- @closed = false
65
+
40
66
  @subscriptions = {}
41
67
  @failure = nil
68
+ @connection_attempts = 0
69
+
42
70
  socket
43
71
  end
44
-
72
+
73
+ def hashed_initialize(params)
74
+
75
+ @parameters = refine_params(params)
76
+ @reliable = true
77
+ @reconnect_delay = @parameters[:initial_reconnect_delay]
78
+ @connect_headers = @parameters[:connect_headers]
79
+
80
+ #sets the first host to connect
81
+ change_host
82
+ end
83
+
45
84
  # Syntactic sugar for 'Connection.new' See 'initialize' for usage.
46
85
  def Connection.open(login = '', passcode = '', host = 'localhost', port = 61613, reliable = false, reconnect_delay = 5, connect_headers = {})
47
86
  Connection.new(login, passcode, host, port, reliable, reconnect_delay, connect_headers)
@@ -50,30 +89,158 @@ module Stomp
50
89
  def socket
51
90
  # Need to look into why the following synchronize does not work.
52
91
  #@read_semaphore.synchronize do
92
+
53
93
  s = @socket;
94
+
95
+ s = nil unless connected?
96
+
54
97
  while s.nil? || !@failure.nil?
55
98
  @failure = nil
56
99
  begin
57
- s = TCPSocket.open @host, @port
58
- headers = @connect_headers.clone
59
- headers[:login] = @login
60
- headers[:passcode] = @passcode
100
+ s = open_socket
101
+ @closed = false
102
+
103
+ headers = @connect_headers.clone
104
+ headers[:login] = @login
105
+ headers[:passcode] = @passcode
61
106
  _transmit(s, "CONNECT", headers)
62
107
  @connect = _receive(s)
63
108
  # replay any subscriptions.
64
109
  @subscriptions.each { |k,v| _transmit(s, "SUBSCRIBE", v) }
110
+
111
+ @connection_attempts = 0
65
112
  rescue
66
113
  @failure = $!;
67
114
  s=nil;
68
115
  raise unless @reliable
69
- $stderr.print "connect failed: " + $! +" will retry in #{@reconnect_delay}\n";
116
+ $stderr.print "connect to #{@host} failed: " + $! +" will retry(##{@connection_attempts}) in #{@reconnect_delay}\n";
117
+
118
+ raise "Max number of reconnection attempts reached" if max_reconnect_attempts?
119
+
70
120
  sleep(@reconnect_delay);
121
+
122
+ @connection_attempts += 1
123
+
124
+ if @parameters
125
+ change_host
126
+ increase_reconnect_delay
127
+ end
71
128
  end
72
129
  end
73
130
  @socket = s
74
131
  return s;
75
132
  #end
76
133
  end
134
+
135
+ def connected?
136
+ begin
137
+ test_socket = TCPSocket.open @host, @port
138
+ test_socket.close
139
+ open?
140
+ rescue
141
+ false
142
+ end
143
+ end
144
+
145
+ def close_socket
146
+ begin
147
+ @socket.close
148
+ rescue
149
+ #Ignoring if already closed
150
+ end
151
+
152
+ @closed = true
153
+ end
154
+
155
+ def open_socket
156
+ return TCPSocket.open @host, @port unless @ssl
157
+
158
+ ssl_socket
159
+ end
160
+
161
+ def ssl_socket
162
+ require 'openssl' unless defined?(OpenSSL)
163
+
164
+ ctx = OpenSSL::SSL::SSLContext.new
165
+
166
+ # For client certificate authentication:
167
+ # key_path = ENV["STOMP_KEY_PATH"] || "~/stomp_keys"
168
+ # ctx.cert = OpenSSL::X509::Certificate.new("#{key_path}/client.cer")
169
+ # ctx.key = OpenSSL::PKey::RSA.new("#{key_path}/client.keystore")
170
+
171
+ # For server certificate authentication:
172
+ # truststores = OpenSSL::X509::Store.new
173
+ # truststores.add_file("#{key_path}/client.ts")
174
+ # ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER
175
+ # ctx.cert_store = truststores
176
+
177
+ ctx.verify_mode = OpenSSL::SSL::VERIFY_NONE
178
+
179
+ tcp_socket = TCPSocket.new @host, @port
180
+ ssl = OpenSSL::SSL::SSLSocket.new(tcp_socket, ctx)
181
+ ssl.connect
182
+ ssl
183
+ end
184
+
185
+ def refine_params(params)
186
+ params = uncamelized_sym_keys(params)
187
+
188
+ {
189
+ :initial_reconnect_delay => 0.01,
190
+ :max_reconnect_delay => 30.0,
191
+ :use_exponential_back_off => true,
192
+ :back_off_multiplier => 2,
193
+ :max_reconnect_attempts => 0,
194
+ :randomize => false,
195
+ :connect_headers => {},
196
+ :backup => false,
197
+ :timeout => -1
198
+ }.merge(params)
199
+
200
+ end
201
+
202
+ def uncamelized_sym_keys(params)
203
+ uncamelized = {}
204
+ params.each_pair do |key, value|
205
+ key = key.to_s.split(/(?=[A-Z])/).join('_').downcase.to_sym
206
+ uncamelized[key] = value
207
+ end
208
+
209
+ uncamelized
210
+ end
211
+
212
+ def change_host
213
+ @parameters[:hosts].shuffle! if @parameters[:randomize]
214
+
215
+ # Set first as master and send it to the end of array
216
+ current_host = @parameters[:hosts].shift
217
+ @parameters[:hosts] << current_host
218
+
219
+ @ssl = current_host[:ssl]
220
+ @host = current_host[:host]
221
+ @port = current_host[:port] || default_port(@ssl)
222
+ @login = current_host[:login] || ""
223
+ @passcode = current_host[:passcode] || ""
224
+
225
+ end
226
+
227
+ def default_port(ssl)
228
+ return 61612 if ssl
229
+
230
+ 61613
231
+ end
232
+
233
+ def max_reconnect_attempts?
234
+ !(@parameters.nil? || @parameters[:max_reconnect_attempts].nil?) && @parameters[:max_reconnect_attempts] != 0 && @connection_attempts > @parameters[:max_reconnect_attempts]
235
+ end
236
+
237
+ def increase_reconnect_delay
238
+
239
+ @reconnect_delay *= @parameters[:back_off_multiplier] if @parameters[:use_exponential_back_off]
240
+ @reconnect_delay = @parameters[:max_reconnect_delay] if @reconnect_delay > @parameters[:max_reconnect_delay]
241
+
242
+ @reconnect_delay
243
+ end
77
244
 
78
245
  # Is this connection open?
79
246
  def open?
@@ -145,7 +312,8 @@ module Stomp
145
312
  # Close this connection
146
313
  def disconnect(headers = {})
147
314
  transmit("DISCONNECT", headers)
148
- @closed = true
315
+
316
+ close_socket
149
317
  end
150
318
 
151
319
  # Return a pending message if one is available, otherwise
@@ -233,7 +401,7 @@ module Stomp
233
401
  rescue
234
402
  @failure = $!;
235
403
  raise unless @reliable
236
- $stderr.print "transmit failed: " + $!+"\n";
404
+ $stderr.print "transmit to #{@host} failed: " + $!+"\n";
237
405
  end
238
406
  end
239
407
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: stomp
3
3
  version: !ruby/object:Gem::Version
4
- version: "1.1"
4
+ version: 1.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brian McCallister
@@ -10,7 +10,7 @@ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
12
 
13
- date: 2009-02-27 00:00:00 +01:00
13
+ date: 2009-12-03 00:00:00 -02:00
14
14
  default_executable:
15
15
  dependencies: []
16
16
 
@@ -41,6 +41,8 @@ files:
41
41
  - test/test_helper.rb
42
42
  has_rdoc: true
43
43
  homepage: http://stomp.codehaus.org/
44
+ licenses: []
45
+
44
46
  post_install_message:
45
47
  rdoc_options:
46
48
  - --quiet
@@ -69,9 +71,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
69
71
  requirements: []
70
72
 
71
73
  rubyforge_project:
72
- rubygems_version: 1.3.1
74
+ rubygems_version: 1.3.5
73
75
  signing_key:
74
- specification_version: 2
76
+ specification_version: 3
75
77
  summary: Ruby client for the Stomp messaging protocol
76
78
  test_files:
77
79
  - test/test_client.rb