stomp 1.1.10 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,45 @@
1
+ #
2
+ # Common Stomp 1.1 code.
3
+ #
4
+ require "rubygems" if RUBY_VERSION < "1.9"
5
+ require "stomp"
6
+ #
7
+ module Stomp11Common
8
+ #
9
+ def login()
10
+ ENV['STOMP_USER'] || 'guest'
11
+ end
12
+ #
13
+ def passcode()
14
+ ENV['STOMP_PASSCODE'] || 'guest'
15
+ end
16
+ #
17
+ def host()
18
+ ENV['STOMP_HOST'] || "localhost" # The connect host name
19
+ end
20
+ #
21
+ def port()
22
+ (ENV['STOMP_PORT'] || 62613).to_i # !! The author runs Apollo listening here
23
+ end
24
+ #
25
+ def virt_host()
26
+ ENV['STOMP_VHOST'] || "localhost" # The 1.1 virtual host name
27
+ end
28
+ #
29
+ def get_connection()
30
+ conn_hdrs = {"accept-version" => "1.1", # 1.1 only
31
+ "host" => virt_host, # the vhost
32
+ }
33
+ conn_hash = { :hosts => [
34
+ {:login => login, :passcode => passcode, :host => host, :port => port},
35
+ ],
36
+ :connect_headers => conn_hdrs,
37
+ }
38
+ conn = Stomp::Connection.new(conn_hash)
39
+ end
40
+ #
41
+ def nmsgs()
42
+ (ENV['STOMP_NMSGS'] || 1).to_i # Number of messages
43
+ end
44
+ end
45
+
@@ -0,0 +1,19 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'rubygems'
4
+ require 'stomp'
5
+
6
+ client = Stomp::Client.new("failover://(stomp://:@localhost:61613,stomp://:@remotehost:61613)?initialReconnectDelay=5000&randomize=false&useExponentialBackOff=false")
7
+
8
+ puts "Subscribing to /topic/ronaldo"
9
+
10
+ client.subscribe("/topic/ronaldo") do |msg|
11
+ puts msg.to_s
12
+ puts "----------------"
13
+ end
14
+
15
+ loop do
16
+ sleep(1)
17
+ puts "."
18
+ end
19
+
@@ -0,0 +1,15 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'rubygems'
4
+ require 'stomp'
5
+
6
+ client = Stomp::Client.new("failover://(stomp://:@localhost:61613,stomp://:@remotehost:61613)?initialReconnectDelay=5000&randomize=false&useExponentialBackOff=false")
7
+ message = "ronaldo #{ARGV[0]}"
8
+
9
+ for i in (1..300)
10
+ puts "Sending message"
11
+ client.publish("/topic/ronaldo", "#{i}: #{message}")
12
+ puts "(#{Time.now})Message sent: #{i}"
13
+ sleep 1
14
+ end
15
+
data/lib/stomp.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # -*- encoding: utf-8 -*-
2
+
1
3
  # Copyright 2005-2006 Brian McCallister
2
4
  # Copyright 2006 LogicBlaze Inc.
3
5
  #
@@ -19,6 +21,8 @@ require 'stomp/client'
19
21
  require 'stomp/message'
20
22
  require 'stomp/version'
21
23
  require 'stomp/errors'
24
+ require 'stomp/constants'
25
+ require 'stomp/codec'
22
26
 
23
27
  module Stomp
24
28
  end
data/lib/stomp/client.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # -*- encoding: utf-8 -*-
2
+
1
3
  require 'thread'
2
4
  require 'digest/sha1'
3
5
 
@@ -143,7 +145,7 @@ module Stomp
143
145
  def subscribe(destination, headers = {})
144
146
  raise "No listener given" unless block_given?
145
147
  # use subscription id to correlate messages to subscription. As described in
146
- # the SUBSCRIPTION section of the protocol: http://stomp.codehaus.org/Protocol.
148
+ # the SUBSCRIPTION section of the protocol: http://stomp.github.com/.
147
149
  # If no subscription id is provided, generate one.
148
150
  set_subscription_id_if_missing(destination, headers)
149
151
  if @listeners[headers[:id]]
@@ -180,7 +182,12 @@ module Stomp
180
182
  end
181
183
  @connection.ack message.headers['message-id'], headers
182
184
  end
183
-
185
+
186
+ # Stomp 1.1+ NACK
187
+ def nack(message_id, headers = {})
188
+ @connection.nack message_id, headers
189
+ end
190
+
184
191
  # Unreceive a message, sending it back to its queue or to the DLQ
185
192
  #
186
193
  def unreceive(message, options = {})
@@ -238,11 +245,36 @@ module Stomp
238
245
  @listener_thread && !!@listener_thread.status
239
246
  end
240
247
 
248
+ # Convenience method
249
+ def set_logger(logger)
250
+ @connection.set_logger(logger)
251
+ end
252
+
253
+ # Convenience method
254
+ def protocol()
255
+ @connection.protocol
256
+ end
257
+
258
+ # Convenience method
259
+ def valid_utf8?(s)
260
+ @connection.valid_utf8?(s)
261
+ end
262
+
263
+ # Convenience method for clients
264
+ def sha1(data)
265
+ @connection.sha1(data)
266
+ end
267
+
268
+ # Convenience method for clients
269
+ def uuid()
270
+ @connection.uuid()
271
+ end
272
+
241
273
  private
242
274
  # Set a subscription id in the headers hash if one does not already exist.
243
275
  # For simplicities sake, all subscriptions have a subscription ID.
244
276
  # setting an id in the SUBSCRIPTION header is described in the stomp protocol docs:
245
- # http://stomp.codehaus.org/Protocol
277
+ # http://stomp.github.com/
246
278
  def set_subscription_id_if_missing(destination, headers)
247
279
  headers[:id] = headers[:id] ? headers[:id] : headers['id']
248
280
  if headers[:id] == nil
@@ -309,7 +341,7 @@ module Stomp
309
341
  # For backward compatibility, some messages may already exist with no
310
342
  # subscription id, in which case we can attempt to synthesize one.
311
343
  set_subscription_id_if_missing(message.headers['destination'], message.headers)
312
- subscription_id = message.headers['id']
344
+ subscription_id = message.headers[:id]
313
345
  end
314
346
  @listeners[subscription_id]
315
347
  end
@@ -0,0 +1,41 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ module Stomp
4
+ #
5
+ # == Purpose
6
+ #
7
+ # A general CODEC for STOMP 1.1 header keys and values.
8
+ #
9
+ # See:
10
+ #
11
+ # * http://stomp.github.com/index.html
12
+ #
13
+ # for encode/decode rules.
14
+ #
15
+ class HeaderCodec
16
+
17
+ # Encode header data per STOMP 1.1 specification
18
+ def self.encode(in_string = nil)
19
+ return in_string unless in_string
20
+ ev = Stomp::ENCODE_VALUES # avoid typing below
21
+ os = in_string + ""
22
+ 0.step(ev.length-2,2) do |i|
23
+ os.gsub!(ev[i], ev[i+1])
24
+ end
25
+ os
26
+ end
27
+
28
+ # Decode header data per STOMP 1.1 specification
29
+ def self.decode(in_string = nil)
30
+ return in_string unless in_string
31
+ ev = Stomp::DECODE_VALUES # avoid typing below
32
+ os = in_string + ""
33
+ 0.step(ev.length-2,2) do |i|
34
+ os.gsub!(ev[i+1], ev[i])
35
+ end
36
+ os
37
+ end
38
+
39
+ end # of class HeaderCodec
40
+ end # of module Stomp
41
+
@@ -1,6 +1,9 @@
1
+ # -*- encoding: utf-8 -*-
2
+
1
3
  require 'socket'
2
4
  require 'timeout'
3
5
  require 'io/wait'
6
+ require 'digest/sha1'
4
7
 
5
8
  module Stomp
6
9
 
@@ -9,6 +12,10 @@ module Stomp
9
12
  class Connection
10
13
  attr_reader :connection_frame
11
14
  attr_reader :disconnect_receipt
15
+ attr_reader :protocol
16
+ attr_reader :session
17
+ attr_reader :hb_received # Heartbeat received on time
18
+ attr_reader :hb_sent # Heartbeat sent successfully
12
19
  #alias :obj_send :send
13
20
 
14
21
  def self.default_port(ssl)
@@ -33,6 +40,7 @@ module Stomp
33
40
  # {:login => "login1", :passcode => "passcode1", :host => "localhost", :port => 61616, :ssl => false},
34
41
  # {:login => "login2", :passcode => "passcode2", :host => "remotehost", :port => 61617, :ssl => false}
35
42
  # ],
43
+ # :reliable => true,
36
44
  # :initial_reconnect_delay => 0.01,
37
45
  # :max_reconnect_delay => 30.0,
38
46
  # :use_exponential_back_off => true,
@@ -40,7 +48,7 @@ module Stomp
40
48
  # :max_reconnect_attempts => 0,
41
49
  # :randomize => false,
42
50
  # :backup => false,
43
- # :timeout => -1,
51
+ # :connect_timeout => 0,
44
52
  # :connect_headers => {},
45
53
  # :parse_timeout => 5,
46
54
  # :logger => nil,
@@ -59,6 +67,10 @@ module Stomp
59
67
  #
60
68
  def initialize(login = '', passcode = '', host = 'localhost', port = 61613, reliable = false, reconnect_delay = 5, connect_headers = {})
61
69
  @received_messages = []
70
+ @protocol = Stomp::SPL_10 # Assumed at first
71
+ @hb_received = true # Assumed at first
72
+ @hb_sent = true # Assumed at first
73
+ @hbs = @hbr = false # Sending/Receiving heartbeats. Assume no for now.
62
74
 
63
75
  if login.is_a?(Hash)
64
76
  hashed_initialize(login)
@@ -73,6 +85,7 @@ module Stomp
73
85
  @ssl = false
74
86
  @parameters = nil
75
87
  @parse_timeout = 5 # To override, use hashed parameters
88
+ @connect_timeout = 0 # To override, use hashed parameters
76
89
  @logger = nil # To override, use hashed parameters
77
90
  end
78
91
 
@@ -92,10 +105,11 @@ module Stomp
92
105
  def hashed_initialize(params)
93
106
 
94
107
  @parameters = refine_params(params)
95
- @reliable = true
108
+ @reliable = @parameters[:reliable]
96
109
  @reconnect_delay = @parameters[:initial_reconnect_delay]
97
110
  @connect_headers = @parameters[:connect_headers]
98
111
  @parse_timeout = @parameters[:parse_timeout]
112
+ @connect_timeout = @parameters[:connect_timeout]
99
113
  @logger = @parameters[:logger]
100
114
  #sets the first host to connect
101
115
  change_host
@@ -155,6 +169,7 @@ module Stomp
155
169
 
156
170
  default_params = {
157
171
  :connect_headers => {},
172
+ :reliable => true,
158
173
  # Failover parameters
159
174
  :initial_reconnect_delay => 0.01,
160
175
  :max_reconnect_delay => 30.0,
@@ -163,7 +178,7 @@ module Stomp
163
178
  :max_reconnect_attempts => 0,
164
179
  :randomize => false,
165
180
  :backup => false,
166
- :timeout => -1,
181
+ :connect_timeout => 0,
167
182
  # Parse Timeout
168
183
  :parse_timeout => 5
169
184
  }
@@ -211,8 +226,11 @@ module Stomp
211
226
 
212
227
  # Begin a transaction, requires a name for the transaction
213
228
  def begin(name, headers = {})
229
+ raise Stomp::Error::NoCurrentConnection if closed?
230
+ headers = headers.symbolize_keys
214
231
  headers[:transaction] = name
215
- transmit("BEGIN", headers)
232
+ _headerCheck(headers)
233
+ transmit(Stomp::CMD_BEGIN, headers)
216
234
  end
217
235
 
218
236
  # Acknowledge a message, used when a subscription has specified
@@ -220,40 +238,83 @@ module Stomp
220
238
  #
221
239
  # Accepts a transaction header ( :transaction => 'some_transaction_id' )
222
240
  def ack(message_id, headers = {})
223
- headers['message-id'] = message_id
224
- transmit("ACK", headers)
241
+ raise Stomp::Error::NoCurrentConnection if closed?
242
+ raise Stomp::Error::MessageIDRequiredError if message_id.nil? || message_id == ""
243
+ headers = headers.symbolize_keys
244
+ headers[:'message-id'] = message_id
245
+ if @protocol >= Stomp::SPL_11
246
+ raise Stomp::Error::SubscriptionRequiredError unless headers[:subscription]
247
+ end
248
+ _headerCheck(headers)
249
+ transmit(Stomp::CMD_ACK, headers)
250
+ end
251
+
252
+ # STOMP 1.1+ NACK
253
+ def nack(message_id, headers = {})
254
+ raise Stomp::Error::NoCurrentConnection if closed?
255
+ raise Stomp::Error::UnsupportedProtocolError if @protocol == Stomp::SPL_10
256
+ raise Stomp::Error::MessageIDRequiredError if message_id.nil? || message_id == ""
257
+ headers = headers.symbolize_keys
258
+ headers[:'message-id'] = message_id
259
+ raise Stomp::Error::SubscriptionRequiredError unless headers[:subscription]
260
+ _headerCheck(headers)
261
+ transmit(Stomp::CMD_NACK, headers)
225
262
  end
226
263
 
227
264
  # Commit a transaction by name
228
265
  def commit(name, headers = {})
266
+ raise Stomp::Error::NoCurrentConnection if closed?
267
+ headers = headers.symbolize_keys
229
268
  headers[:transaction] = name
230
- transmit("COMMIT", headers)
269
+ _headerCheck(headers)
270
+ transmit(Stomp::CMD_COMMIT, headers)
231
271
  end
232
272
 
233
273
  # Abort a transaction by name
234
274
  def abort(name, headers = {})
275
+ raise Stomp::Error::NoCurrentConnection if closed?
276
+ headers = headers.symbolize_keys
235
277
  headers[:transaction] = name
236
- transmit("ABORT", headers)
278
+ _headerCheck(headers)
279
+ transmit(Stomp::CMD_ABORT, headers)
237
280
  end
238
281
 
239
282
  # Subscribe to a destination, must specify a name
240
283
  def subscribe(name, headers = {}, subId = nil)
284
+ raise Stomp::Error::NoCurrentConnection if closed?
285
+ headers = headers.symbolize_keys
241
286
  headers[:destination] = name
242
- transmit("SUBSCRIBE", headers)
287
+ if @protocol >= Stomp::SPL_11
288
+ raise Stomp::Error::SubscriptionRequiredError if (headers[:id].nil? && subId.nil?)
289
+ headers[:id] = subId if headers[:id].nil?
290
+ end
291
+ _headerCheck(headers)
292
+ if @logger && @logger.respond_to?(:on_subscribe)
293
+ @logger.on_subscribe(log_params, headers)
294
+ end
243
295
 
244
296
  # Store the sub so that we can replay if we reconnect.
245
297
  if @reliable
246
298
  subId = name if subId.nil?
299
+ raise Stomp::Error::DuplicateSubscription if @subscriptions[subId]
247
300
  @subscriptions[subId] = headers
248
301
  end
302
+
303
+ transmit(Stomp::CMD_SUBSCRIBE, headers)
249
304
  end
250
305
 
251
- # Unsubscribe from a destination, must specify a name
252
- def unsubscribe(name, headers = {}, subId = nil)
253
- headers[:destination] = name
254
- transmit("UNSUBSCRIBE", headers)
306
+ # Unsubscribe from a destination, which must be specified
307
+ def unsubscribe(dest, headers = {}, subId = nil)
308
+ raise Stomp::Error::NoCurrentConnection if closed?
309
+ headers = headers.symbolize_keys
310
+ headers[:destination] = dest
311
+ if @protocol >= Stomp::SPL_11
312
+ raise Stomp::Error::SubscriptionRequiredError if (headers[:id].nil? && subId.nil?)
313
+ end
314
+ _headerCheck(headers)
315
+ transmit(Stomp::CMD_UNSUBSCRIBE, headers)
255
316
  if @reliable
256
- subId = name if subId.nil?
317
+ subId = dest if subId.nil?
257
318
  @subscriptions.delete(subId)
258
319
  end
259
320
  end
@@ -263,8 +324,14 @@ module Stomp
263
324
  # To disable content length header ( :suppress_content_length => true )
264
325
  # Accepts a transaction header ( :transaction => 'some_transaction_id' )
265
326
  def publish(destination, message, headers = {})
327
+ raise Stomp::Error::NoCurrentConnection if closed?
328
+ headers = headers.symbolize_keys
266
329
  headers[:destination] = destination
267
- transmit("SEND", headers, message)
330
+ _headerCheck(headers)
331
+ if @logger && @logger.respond_to?(:on_publish)
332
+ @logger.on_publish(log_params, message, headers)
333
+ end
334
+ transmit(Stomp::CMD_SEND, headers, message)
268
335
  end
269
336
 
270
337
  def obj_send(*args)
@@ -282,6 +349,7 @@ module Stomp
282
349
  # Accepts a limit number of redeliveries option ( :max_redeliveries => 6 )
283
350
  # Accepts a force client acknowledgement option (:force_client_ack => true)
284
351
  def unreceive(message, options = {})
352
+ raise Stomp::Error::NoCurrentConnection if closed?
285
353
  options = { :dead_letter_queue => "/queue/DLQ", :max_redeliveries => 6 }.merge options
286
354
  # Lets make sure all keys are symbols
287
355
  message.headers = message.headers.symbolize_keys
@@ -318,8 +386,14 @@ module Stomp
318
386
 
319
387
  # Close this connection
320
388
  def disconnect(headers = {})
321
- transmit("DISCONNECT", headers)
389
+ raise Stomp::Error::NoCurrentConnection if closed?
322
390
  headers = headers.symbolize_keys
391
+ _headerCheck(headers)
392
+ if @protocol >= Stomp::SPL_11
393
+ @st.kill if @st # Kill ticker thread if any
394
+ @rt.kill if @rt # Kill ticker thread if any
395
+ end
396
+ transmit(Stomp::CMD_DISCONNECT, headers)
323
397
  @disconnect_receipt = receive if headers[:receipt]
324
398
  if @logger && @logger.respond_to?(:on_disconnect)
325
399
  @logger.on_disconnect(log_params)
@@ -330,6 +404,7 @@ module Stomp
330
404
  # Return a pending message if one is available, otherwise
331
405
  # return nil
332
406
  def poll
407
+ raise Stomp::Error::NoCurrentConnection if closed?
333
408
  # No need for a read lock here. The receive method eventually fulfills
334
409
  # that requirement.
335
410
  return nil if @socket.nil? || !@socket.ready?
@@ -357,6 +432,7 @@ module Stomp
357
432
  end
358
433
 
359
434
  def receive
435
+ raise Stomp::Error::NoCurrentConnection if closed?
360
436
  super_result = __old_receive
361
437
  if super_result.nil? && @reliable && !closed?
362
438
  errstr = "connection.receive returning EOF as nil - resetting connection.\n"
@@ -368,18 +444,64 @@ module Stomp
368
444
  @socket = nil
369
445
  super_result = __old_receive
370
446
  end
447
+ #
448
+ if @logger && @logger.respond_to?(:on_receive)
449
+ @logger.on_receive(log_params, super_result)
450
+ end
371
451
  return super_result
372
452
  end
373
453
 
454
+ # Convenience method
455
+ def set_logger(logger)
456
+ @logger = logger
457
+ end
458
+
459
+ # Convenience method
460
+ def valid_utf8?(s)
461
+ case RUBY_VERSION
462
+ when /1\.8/
463
+ rv = _valid_utf8?(s)
464
+ else
465
+ rv = s.encoding.name != Stomp::UTF8 ? false : s.valid_encoding?
466
+ end
467
+ rv
468
+ end
469
+
470
+ # Convenience method for clients, return a SHA1 digest for arbitrary data
471
+ def sha1(data)
472
+ Digest::SHA1.hexdigest(data)
473
+ end
474
+
475
+ # Convenience method for clients, return a type 4 UUID.
476
+ def uuid()
477
+ b = []
478
+ 0.upto(15) do |i|
479
+ b << rand(255)
480
+ end
481
+ b[6] = (b[6] & 0x0F) | 0x40
482
+ b[8] = (b[8] & 0xbf) | 0x80
483
+ # 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
484
+ rs = sprintf("%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x%02x%02x",
485
+ b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7], b[8], b[9], b[10], b[11], b[12], b[13], b[14], b[15])
486
+ rs
487
+ end
488
+
374
489
  private
375
490
 
376
491
  def _receive( read_socket )
377
492
  @read_semaphore.synchronize do
378
- # Throw away leading newlines, which are actually trailing
379
- # newlines from the preceding message.
493
+ # Throw away leading newlines, which are perhaps trailing
494
+ # newlines from the preceding message, or alterantely a 1.1+ server
495
+ # heartbeat.
380
496
  begin
381
497
  last_char = read_socket.getc
382
498
  return nil if last_char.nil?
499
+ if @protocol >= Stomp::SPL_11
500
+ plc = parse_char(last_char)
501
+ if plc == "\n" # Server Heartbeat
502
+ @lr = Time.now.to_f if @hbr
503
+ end
504
+ end
383
505
  end until parse_char(last_char) != "\n"
384
506
  read_socket.ungetc(last_char)
385
507
 
@@ -409,8 +531,17 @@ module Stomp
409
531
  message_body << char while (char = parse_char(read_socket.getc)) != "\0"
410
532
  end
411
533
 
534
+ if @protocol >= Stomp::SPL_11
535
+ @lr = Time.now.to_f if @hbr
536
+ end
537
+
412
538
  # Adds the excluded \n and \0 and tries to create a new message with it
413
- Message.new(message_header + "\n" + message_body + "\0")
539
+ msg = Message.new(message_header + "\n" + message_body + "\0", @protocol >= Stomp::SPL_11)
540
+ #
541
+ if @protocol >= Stomp::SPL_11 && msg.command != Stomp::CMD_CONNECTED
542
+ msg.headers = _decodeHeaders(msg.headers)
543
+ end
544
+ msg
414
545
  end
415
546
  end
416
547
  end
@@ -442,6 +573,9 @@ module Stomp
442
573
  end
443
574
 
444
575
  def _transmit(used_socket, command, headers = {}, body = '')
576
+ if @protocol >= Stomp::SPL_11 && command != Stomp::CMD_CONNECT
577
+ headers = _encodeHeaders(headers)
578
+ end
445
579
  @transmit_semaphore.synchronize do
446
580
  # Handle nil body
447
581
  body = '' if body.nil?
@@ -457,18 +591,33 @@ module Stomp
457
591
  # For more information refer to http://juretta.com/log/2009/05/24/activemq-jms-stomp/
458
592
  # Lets send this header in the message, so it can maintain state when using unreceive
459
593
  headers['content-length'] = "#{body_length_bytes}" unless headers[:suppress_content_length]
460
-
594
+ headers['content-type'] = "text/plain; charset=UTF-8" unless headers['content-type']
461
595
  used_socket.puts command
462
- headers.each {|k,v| used_socket.puts "#{k}:#{v}" }
463
- used_socket.puts "content-type: text/plain; charset=UTF-8"
596
+ headers.each do |k,v|
597
+ if v.is_a?(Array)
598
+ v.each do |e|
599
+ used_socket.puts "#{k}:#{e}"
600
+ end
601
+ else
602
+ used_socket.puts "#{k}:#{v}"
603
+ end
604
+ end
464
605
  used_socket.puts
465
606
  used_socket.write body
466
607
  used_socket.write "\0"
608
+
609
+ if @protocol >= Stomp::SPL_11
610
+ @ls = Time.now.to_f if @hbs
611
+ end
612
+
467
613
  end
468
614
  end
469
615
 
470
616
  def open_tcp_socket
471
- tcp_socket = TCPSocket.open @host, @port
617
+ tcp_socket = nil
618
+ Timeout::timeout(@connect_timeout, Stomp::Error::SocketOpenTimeout) do
619
+ tcp_socket = TCPSocket.open @host, @port
620
+ end
472
621
 
473
622
  tcp_socket
474
623
  end
@@ -489,8 +638,10 @@ module Stomp
489
638
  # ctx.cert_store = truststores
490
639
 
491
640
  ctx.verify_mode = OpenSSL::SSL::VERIFY_NONE
492
-
493
- ssl = OpenSSL::SSL::SSLSocket.new(open_tcp_socket, ctx)
641
+ ssl = nil
642
+ Timeout::timeout(@connect_timeout, Stomp::Error::SocketOpenTimeout) do
643
+ ssl = OpenSSL::SSL::SSLSocket.new(open_tcp_socket, ctx)
644
+ end
494
645
  def ssl.ready?
495
646
  ! @rbuffer.empty? || @io.ready?
496
647
  end
@@ -524,18 +675,23 @@ module Stomp
524
675
  end
525
676
 
526
677
  def connect(used_socket)
678
+ @connect_headers = {} unless @connect_headers # Caller said nil/false
527
679
  headers = @connect_headers.clone
528
680
  headers[:login] = @login
529
681
  headers[:passcode] = @passcode
682
+ _pre_connect
530
683
  _transmit(used_socket, "CONNECT", headers)
531
684
  @connection_frame = _receive(used_socket)
685
+ _post_connect
532
686
  @disconnect_receipt = nil
687
+ @session = @connection_frame.headers["session"] if @connection_frame
533
688
  # replay any subscriptions.
534
- @subscriptions.each { |k,v| _transmit(used_socket, "SUBSCRIBE", v) }
689
+ @subscriptions.each { |k,v| _transmit(used_socket, Stomp::CMD_SUBSCRIBE, v) }
535
690
  end
536
691
 
537
692
  def log_params
538
- lparms = @parameters.clone
693
+ lparms = @parameters.clone if @parameters
694
+ lparms = {} unless lparms
539
695
  lparms[:cur_host] = @host
540
696
  lparms[:cur_port] = @port
541
697
  lparms[:cur_login] = @login
@@ -547,7 +703,445 @@ module Stomp
547
703
  #
548
704
  lparms
549
705
  end
550
- end
551
706
 
552
- end
707
+ def _pre_connect
708
+ @connect_headers = @connect_headers.symbolize_keys
709
+ raise Stomp::Error::ProtocolErrorConnect if (@connect_headers[:"accept-version"] && !@connect_headers[:host])
710
+ raise Stomp::Error::ProtocolErrorConnect if (!@connect_headers[:"accept-version"] && @connect_headers[:host])
711
+ return unless (@connect_headers[:"accept-version"] && @connect_headers[:host]) # 1.0
712
+ # Try 1.1 or greater
713
+ okvers = []
714
+ avers = @connect_headers[:"accept-version"].split(",")
715
+ avers.each do |nver|
716
+ if Stomp::SUPPORTED.index(nver)
717
+ okvers << nver
718
+ end
719
+ end
720
+ raise Stomp::Error::UnsupportedProtocolError if okvers == []
721
+ @connect_headers[:"accept-version"] = okvers.join(",") # This goes to server
722
+ # Heartbeats - pre connect
723
+ return unless @connect_headers[:"heart-beat"]
724
+ _validate_hbheader()
725
+ end
726
+
727
+ def _post_connect
728
+ return unless (@connect_headers[:"accept-version"] && @connect_headers[:host])
729
+ return if @connection_frame.command == Stomp::CMD_ERROR
730
+ cfh = @connection_frame.headers.symbolize_keys
731
+ @protocol = cfh[:version]
732
+ # Should not happen, but check anyway
733
+ raise Stomp::Error::UnsupportedProtocolError unless Stomp::SUPPORTED.index(@protocol)
734
+ # Heartbeats
735
+ return unless @connect_headers[:"heart-beat"]
736
+ _init_heartbeats()
737
+ end
738
+
739
+ def _validate_hbheader()
740
+ return if @connect_headers[:"heart-beat"] == "0,0" # Caller does not want heartbeats. OK.
741
+ parts = @connect_headers[:"heart-beat"].split(",")
742
+ if (parts.size != 2) || (parts[0] != parts[0].to_i.to_s) || (parts[1] != parts[1].to_i.to_s)
743
+ raise Stomp::Error::InvalidHeartBeatHeaderError
744
+ end
745
+ end
746
+
747
+ def _init_heartbeats()
748
+ return if @connect_headers[:"heart-beat"] == "0,0" # Caller does not want heartbeats. OK.
749
+ #
750
+ @cx = @cy = @sx = @sy = 0, # Variable names as in spec
751
+ #
752
+ @sti = @rti = 0.0 # Send/Receive ticker interval.
753
+ #
754
+ @ls = @lr = -1.0 # Last send/receive time (from Time.now.to_f)
755
+ #
756
+ @st = @rt = nil # Send/receive ticker thread
757
+ #
758
+ cfh = @connection_frame.headers.symbolize_keys
759
+ return if cfh[:"heart-beat"] == "0,0" # Server does not want heartbeats
760
+ #
761
+ parts = @connect_headers[:"heart-beat"].split(",")
762
+ @cx = parts[0].to_i
763
+ @cy = parts[1].to_i
764
+ #
765
+ parts = cfh[:"heart-beat"].split(",")
766
+ @sx = parts[0].to_i
767
+ @sy = parts[1].to_i
768
+ # Catch odd situations like someone has used => heart-beat:000,00000
769
+ return if (@cx == 0 && @cy == 0) || (@sx == 0 && @sy == 0)
770
+ #
771
+ @hbs = @hbr = true # Sending/Receiving heartbeats. Assume yes at first.
772
+ # Check for sending
773
+ @hbs = false if @cx == 0 || @sy == 0
774
+ # Check for receiving
775
+ @hbr = false if @sx == 0 || @cy == 0
776
+ # Should not do heartbeats at all
777
+ return if (!@hbs && !@hbr)
778
+ # If sending
779
+ if @hbs
780
+ sm = @cx >= @sy ? @cx : @sy # ticker interval, ms
781
+ @sti = 1000.0 * sm # ticker interval, μs
782
+ @ls = Time.now.to_f # best guess at start
783
+ _start_send_ticker
784
+ end
785
+
786
+ # If receiving
787
+ if @hbr
788
+ rm = @sx >= @cy ? @sx : @cy # ticker interval, ms
789
+ @rti = 1000.0 * rm # ticker interval, μs
790
+ @lr = Time.now.to_f # best guess at start
791
+ _start_receive_ticker
792
+ end
793
+
794
+ end
795
+
796
+ def _start_send_ticker
797
+ sleeptime = @sti / 1000000.0 # Sleep time secs
798
+ @st = Thread.new {
799
+ while true do
800
+ sleep sleeptime
801
+ curt = Time.now.to_f
802
+ if @logger && @logger.respond_to?(:on_hbfire)
803
+ @logger.on_hbfire(log_params, "send_fire", curt)
804
+ end
805
+ delta = curt - @ls
806
+ if delta > (@sti - (@sti/5.0)) / 1000000.0 # Be tolerant (minus)
807
+ if @logger && @logger.respond_to?(:on_hbfire)
808
+ @logger.on_hbfire(log_params, "send_heartbeat", curt)
809
+ end
810
+ # Send a heartbeat
811
+ @transmit_semaphore.synchronize do
812
+ begin
813
+ @socket.puts
814
+ @ls = curt # Update last send
815
+ @hb_sent = true # Reset if necessary
816
+ rescue Exception => sendex
817
+ @hb_sent = false # Set the warning flag
818
+ if @logger && @logger.respond_to?(:on_hbwrite_fail)
819
+ @logger.on_hbwrite_fail(log_params, {"ticker_interval" => @sti,
820
+ "exception" => sendex})
821
+ end
822
+ raise # Re-raise. What else could be done here?
823
+ end
824
+ end
825
+ end
826
+ Thread.pass
827
+ end
828
+ }
829
+ end
830
+
831
+ def _start_receive_ticker
832
+ sleeptime = @rti / 1000000.0 # Sleep time secs
833
+ @rt = Thread.new {
834
+ while true do
835
+ sleep sleeptime
836
+ curt = Time.now.to_f
837
+ if @logger && @logger.respond_to?(:on_hbfire)
838
+ @logger.on_hbfire(log_params, "receive_fire", curt)
839
+ end
840
+ delta = curt - @lr
841
+ if delta > ((@rti + (@rti/5.0)) / 1000000.0) # Be tolerant (plus)
842
+ if @logger && @logger.respond_to?(:on_hbfire)
843
+ @logger.on_hbfire(log_params, "receive_heartbeat", curt)
844
+ end
845
+ # Client code could be off doing something else (that is, no reading of
846
+ # the socket has been requested by the caller). Try to handle that case.
847
+ lock = @read_semaphore.try_lock
848
+ if lock
849
+ last_char = @socket.getc
850
+ plc = parse_char(last_char)
851
+ if plc == "\n" # Server Heartbeat
852
+ @lr = Time.now.to_f
853
+ else
854
+ @socket.ungetc(last_char)
855
+ end
856
+ @read_semaphore.unlock
857
+ else
858
+ # Shrug. Have not received one. Just set warning flag.
859
+ @hb_received = false
860
+ if @logger && @logger.respond_to?(:on_hbread_fail)
861
+ @logger.on_hbread_fail(log_params, {"ticker_interval" => @rti})
862
+ end
863
+ end
864
+ else
865
+ @hb_received = true # Reset if necessary
866
+ end
867
+ Thread.pass
868
+ end
869
+ }
870
+ end
871
+
872
+ # Ref:
873
+ # http://unicode.org/mail-arch/unicode-ml/y2003-m02/att-0467/01-The_Algorithm_to_Valide_an_UTF-8_String
874
+ #
875
+ def _valid_utf8?(string)
876
+ case RUBY_VERSION
877
+ when /1\.8\.[56]/
878
+ bytes = []
879
+ 0.upto(string.length-1) {|i|
880
+ bytes << string[i]
881
+ }
882
+ else
883
+ bytes = string.bytes
884
+ end
885
+
886
+ #
887
+ valid = true
888
+ index = -1
889
+ nb_hex = nil
890
+ ni_hex = nil
891
+ state = "start"
892
+ next_byte_save = nil
893
+ #
894
+ bytes.each do |next_byte|
895
+ index += 1
896
+ next_byte_save = next_byte
897
+ ni_hex = sprintf "%x", index
898
+ nb_hex = sprintf "%x", next_byte
899
+ # puts "Top: #{next_byte}(0x#{nb_hex}), index: #{index}(0x#{ni_hex})" if DEBUG
900
+ case state
901
+
902
+ # State: 'start'
903
+ # The 'start' state:
904
+ # * handles all occurrences of valid single byte characters i.e., the ASCII character set
905
+ # * provides state transition logic for start bytes of valid characters with 2-4 bytes
906
+ # * signals a validation failure for all other single bytes
907
+ #
908
+ when "start"
909
+ # puts "state: start" if DEBUG
910
+ case next_byte
911
+
912
+ # ASCII
913
+ # * Input = 0x00-0x7F : change state to START
914
+ when (0x00..0x7f)
915
+ # puts "state: start 1" if DEBUG
916
+ state = "start"
917
+
918
+ # Start byte of two byte characters
919
+ # * Input = 0xC2-0xDF: change state to A
920
+ when (0xc2..0xdf)
921
+ # puts "state: start 2" if DEBUG
922
+ state = "a"
923
+
924
+ # Start byte of some three byte characters
925
+ # * Input = 0xE1-0xEC, 0xEE-0xEF: change state to B
926
+ when (0xe1..0xec)
927
+ # puts "state: start 3" if DEBUG
928
+ state = "b"
929
+ when (0xee..0xef)
930
+ # puts "state: start 4" if DEBUG
931
+ state = "b"
932
+
933
+ # Start byte of special three byte characters
934
+ # * Input = 0xE0: change state to C
935
+ when 0xe0
936
+ # puts "state: start 5" if DEBUG
937
+ state = "c"
938
+
939
+ # Start byte of the remaining three byte characters
940
+ # * Input = 0xED: change state to D
941
+ when 0xed
942
+ # puts "state: start 6" if DEBUG
943
+ state = "d"
944
+
945
+ # Start byte of some four byte characters
946
+ # * Input = 0xF1-0xF3:change state to E
947
+ when (0xf1..0xf3)
948
+ # puts "state: start 7" if DEBUG
949
+ state = "e"
950
+
951
+ # Start byte of special four byte characters
952
+ # * Input = 0xF0: change state to F
953
+ when 0xf0
954
+ # puts "state: start 8" if DEBUG
955
+ state = "f"
956
+
957
+ # Start byte of very special four byte characters
958
+ # * Input = 0xF4: change state to G
959
+ when 0xf4
960
+ # puts "state: start 9" if DEBUG
961
+ state = "g"
962
+
963
+ # All other single characters are invalid
964
+ # * Input = Others (0x80-0xBF,0xC0-0xC1, 0xF5-0xFF): ERROR
965
+ else
966
+ valid = false
967
+ break
968
+ end # of the inner case, the 'start' state
969
+
970
+ # The last continuation byte of a 2, 3, or 4 byte character
971
+ # State: 'a'
972
+ # o Input = 0x80-0xBF: change state to START
973
+ # o Others: ERROR
974
+ when "a"
975
+ # puts "state: a" if DEBUG
976
+ if (0x80..0xbf) === next_byte
977
+ state = "start"
978
+ else
979
+ valid = false
980
+ break
981
+ end
982
+
983
+ # The first continuation byte for most 3 byte characters
984
+ # (those with start bytes in: 0xe1-0xec or 0xee-0xef)
985
+ # State: 'b'
986
+ # o Input = 0x80-0xBF: change state to A
987
+ # o Others: ERROR
988
+ when "b"
989
+ # puts "state: b" if DEBUG
990
+ if (0x80..0xbf) === next_byte
991
+ state = "a"
992
+ else
993
+ valid = false
994
+ break
995
+ end
996
+
997
+ # The first continuation byte for some special 3 byte characters
998
+ # (those with start byte 0xe0)
999
+ # State: 'c'
1000
+ # o Input = 0xA0-0xBF: change state to A
1001
+ # o Others: ERROR
1002
+ when "c"
1003
+ # puts "state: c" if DEBUG
1004
+ if (0xa0..0xbf) === next_byte
1005
+ state = "a"
1006
+ else
1007
+ valid = false
1008
+ break
1009
+ end
1010
+
1011
+ # The first continuation byte for the remaining 3 byte characters
1012
+ # (those with start byte 0xed)
1013
+ # State: 'd'
1014
+ # o Input = 0x80-0x9F: change state to A
1015
+ # o Others: ERROR
1016
+ when "d"
1017
+ # puts "state: d" if DEBUG
1018
+ if (0x80..0x9f) === next_byte
1019
+ state = "a"
1020
+ else
1021
+ valid = false
1022
+ break
1023
+ end
1024
+
1025
+ # The first continuation byte for some 4 byte characters
1026
+ # (those with start bytes in: 0xf1-0xf3)
1027
+ # State: 'e'
1028
+ # o Input = 0x80-0xBF: change state to B
1029
+ # o Others: ERROR
1030
+ when "e"
1031
+ # puts "state: e" if DEBUG
1032
+ if (0x80..0xbf) === next_byte
1033
+ state = "b"
1034
+ else
1035
+ valid = false
1036
+ break
1037
+ end
1038
+
1039
+ # The first continuation byte for some special 4 byte characters
1040
+ # (those with start byte 0xf0)
1041
+ # State: 'f'
1042
+ # o Input = 0x90-0xBF: change state to B
1043
+ # o Others: ERROR
1044
+ when "f"
1045
+ # puts "state: f" if DEBUG
1046
+ if (0x90..0xbf) === next_byte
1047
+ state = "b"
1048
+ else
1049
+ valid = false
1050
+ break
1051
+ end
1052
+
1053
+ # The first continuation byte for the remaining 4 byte characters
1054
+ # (those with start byte 0xf4)
1055
+ # State: 'g'
1056
+ # o Input = 0x80-0x8F: change state to B
1057
+ # o Others: ERROR
1058
+ when "g"
1059
+ # puts "state: g" if DEBUG
1060
+ if (0x80..0x8f) === next_byte
1061
+ state = "b"
1062
+ else
1063
+ valid = false
1064
+ break
1065
+ end
1066
+
1067
+ #
1068
+ else
1069
+ raise RuntimeError, "state: default"
1070
+ end
1071
+ end
1072
+ #
1073
+ # puts "State at end: #{state}" if DEBUG
1074
+ # Catch truncation at end of string
1075
+ if valid and state != 'start'
1076
+ # puts "Resetting valid value" if DEBUG
1077
+ valid = false
1078
+ end
1079
+ #
1080
+ valid
1081
+ end # of _valid_utf8?
1082
+
1083
+ def _headerCheck(h)
1084
+ return if @protocol == Stomp::SPL_10 # Do nothing for this environment
1085
+ #
1086
+ h.each_pair do |k,v|
1087
+ # Keys here are symbolized
1088
+ ks = k.to_s
1089
+ ks.force_encoding(Stomp::UTF8) if ks.respond_to?(:force_encoding)
1090
+ raise Stomp::Error::UTF8ValidationError unless valid_utf8?(ks)
1091
+ #
1092
+ if v.is_a?(Array)
1093
+ v.each do |e|
1094
+ e.force_encoding(Stomp::UTF8) if e.respond_to?(:force_encoding)
1095
+ raise Stomp::Error::UTF8ValidationError unless valid_utf8?(e)
1096
+ end
1097
+ else
1098
+ vs = v.to_s # Values are usually Strings, but could be TrueClass or Symbol
1099
+ vs.force_encoding(Stomp::UTF8) if vs.respond_to?(:force_encoding)
1100
+ raise Stomp::Error::UTF8ValidationError unless valid_utf8?(vs)
1101
+ end
1102
+ end
1103
+ end
1104
+
1105
+ #
1106
+ def _encodeHeaders(h)
1107
+ eh = {}
1108
+ h.each_pair do |k,v|
1109
+ # Keys are symbolized
1110
+ ks = k.to_s
1111
+ if v.is_a?(Array)
1112
+ kenc = Stomp::HeaderCodec::encode(ks)
1113
+ eh[kenc] = []
1114
+ v.each do |e|
1115
+ eh[kenc] << Stomp::HeaderCodec::encode(e)
1116
+ end
1117
+ else
1118
+ vs = v.to_s
1119
+ eh[Stomp::HeaderCodec::encode(ks)] = Stomp::HeaderCodec::encode(vs)
1120
+ end
1121
+ end
1122
+ eh
1123
+ end
1124
+
1125
+ #
1126
+ def _decodeHeaders(h)
1127
+ dh = {}
1128
+ h.each_pair do |k,v|
1129
+ # Keys here are NOT! symbolized
1130
+ if v.is_a?(Array)
1131
+ kdec = Stomp::HeaderCodec::decode(k)
1132
+ dh[kdec] = []
1133
+ v.each do |e|
1134
+ dh[kdec] << Stomp::HeaderCodec::decode(e)
1135
+ end
1136
+ else
1137
+ vs = v.to_s
1138
+ dh[Stomp::HeaderCodec::decode(k)] = Stomp::HeaderCodec::decode(vs)
1139
+ end
1140
+ end
1141
+ dh
1142
+ end
1143
+
1144
+ end # class
1145
+
1146
+ end # module
553
1147