stomp 1.2.4 → 1.2.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.
Files changed (56) hide show
  1. data/CHANGELOG.rdoc +11 -0
  2. data/README.rdoc +38 -26
  3. data/Rakefile +3 -0
  4. data/bin/catstomp +34 -34
  5. data/bin/stompcat +36 -36
  6. data/examples/client11_ex1.rb +64 -55
  7. data/examples/client11_putget1.rb +47 -35
  8. data/examples/conn11_ex1.rb +59 -51
  9. data/examples/conn11_ex2.rb +59 -50
  10. data/examples/conn11_hb1.rb +35 -26
  11. data/examples/consumer.rb +25 -12
  12. data/examples/get11conn_ex1.rb +97 -89
  13. data/examples/get11conn_ex2.rb +55 -47
  14. data/examples/logexamp.rb +66 -52
  15. data/examples/logexamp_ssl.rb +66 -52
  16. data/examples/publisher.rb +21 -10
  17. data/examples/put11conn_ex1.rb +35 -24
  18. data/examples/putget11_rh1.rb +66 -56
  19. data/examples/slogger.rb +65 -52
  20. data/examples/ssl_uc1.rb +24 -13
  21. data/examples/ssl_uc1_ciphers.rb +28 -15
  22. data/examples/ssl_uc2.rb +26 -16
  23. data/examples/ssl_uc2_ciphers.rb +31 -18
  24. data/examples/ssl_uc3.rb +25 -14
  25. data/examples/ssl_uc3_ciphers.rb +31 -18
  26. data/examples/ssl_uc4.rb +26 -15
  27. data/examples/ssl_uc4_ciphers.rb +32 -19
  28. data/examples/ssl_ucx_default_ciphers.rb +25 -12
  29. data/examples/stomp11_common.rb +16 -15
  30. data/examples/topic_consumer.rb +23 -10
  31. data/examples/topic_publisher.rb +22 -8
  32. data/lib/client/utils.rb +116 -0
  33. data/lib/connection/heartbeats.rb +173 -0
  34. data/lib/connection/netio.rb +322 -0
  35. data/lib/connection/utf8.rb +294 -0
  36. data/lib/connection/utils.rb +104 -0
  37. data/lib/stomp/client.rb +127 -179
  38. data/lib/stomp/codec.rb +5 -2
  39. data/lib/stomp/connection.rb +109 -865
  40. data/lib/stomp/constants.rb +52 -33
  41. data/lib/stomp/errors.rb +56 -5
  42. data/lib/stomp/ext/hash.rb +4 -0
  43. data/lib/stomp/message.rb +49 -29
  44. data/lib/stomp/sslparams.rb +83 -71
  45. data/lib/stomp/version.rb +3 -1
  46. data/lib/stomp.rb +18 -9
  47. data/stomp.gemspec +58 -3
  48. data/test/test_client.rb +28 -1
  49. data/test/test_codec.rb +8 -2
  50. data/test/test_connection.rb +29 -0
  51. data/test/test_connection1p.rb +31 -16
  52. data/test/test_helper.rb +20 -3
  53. data/test/test_message.rb +8 -3
  54. data/test/test_ssl.rb +10 -4
  55. data/test/tlogger.rb +16 -15
  56. metadata +59 -4
@@ -10,31 +10,39 @@ module Stomp
10
10
  # Low level connection which maps commands and supports
11
11
  # synchronous receives
12
12
  class Connection
13
+
14
+ public
15
+
16
+ # The CONNECTED frame from the broker.
13
17
  attr_reader :connection_frame
18
+
19
+ # Any disconnect RECEIPT frame if requested.
14
20
  attr_reader :disconnect_receipt
21
+
22
+ # The Stomp Protocol version.
15
23
  attr_reader :protocol
24
+
25
+ # A unique session ID, assigned by the broker.
16
26
  attr_reader :session
27
+
28
+ # Heartbeat receive has been on time.
17
29
  attr_reader :hb_received # Heartbeat received on time
30
+
31
+ # Heartbeat send has been successful.
18
32
  attr_reader :hb_sent # Heartbeat sent successfully
33
+
34
+ # Autoflush forces a flush on each transmit. This may be changed
35
+ # dynamically by calling code.
19
36
  attr_accessor :autoflush
20
- #alias :obj_send :send
21
37
 
38
+ # default_port returns the default port used by the gem for TCP or SSL.
22
39
  def self.default_port(ssl)
23
40
  ssl ? 61612 : 61613
24
41
  end
25
42
 
26
- # A new Connection object accepts the following parameters:
43
+ # A new Connection object can be initialized using two forms:
27
44
  #
28
- # login (String, default : '')
29
- # passcode (String, default : '')
30
- # host (String, default : 'localhost')
31
- # port (Integer, default : 61613)
32
- # reliable (Boolean, default : false)
33
- # reconnect_delay (Integer, default : 5)
34
- #
35
- # e.g. c = Connection.new("username", "password", "localhost", 61613, true)
36
- #
37
- # Hash:
45
+ # Hash (this is the recommended Connection initialization method:
38
46
  #
39
47
  # hash = {
40
48
  # :hosts => [
@@ -55,16 +63,18 @@ module Stomp
55
63
  # :logger => nil,
56
64
  # }
57
65
  #
58
- # e.g. c = Connection.new(hash)
66
+ # e.g. c = Stomp::Connection.new(hash)
59
67
  #
60
- # TODO
61
- # Stomp URL :
62
- # A Stomp URL must begin with 'stomp://' and can be in one of the following forms:
68
+ # Positional parameters:
69
+ #
70
+ # login (String, default : '')
71
+ # passcode (String, default : '')
72
+ # host (String, default : 'localhost')
73
+ # port (Integer, default : 61613)
74
+ # reliable (Boolean, default : false)
75
+ # reconnect_delay (Integer, default : 5)
63
76
  #
64
- # stomp://host:port
65
- # stomp://host.domain.tld:port
66
- # stomp://user:pass@host:port
67
- # stomp://user:pass@host.domain.tld:port
77
+ # e.g. c = Stomp::Connection.new("username", "password", "localhost", 61613, true)
68
78
  #
69
79
  def initialize(login = '', passcode = '', host = 'localhost', port = 61613, reliable = false, reconnect_delay = 5, connect_headers = {})
70
80
  @received_messages = []
@@ -105,6 +115,8 @@ module Stomp
105
115
  socket
106
116
  end
107
117
 
118
+ # hashed_initialize prepares a new connection with a Hash of initialization
119
+ # parameters.
108
120
  def hashed_initialize(params)
109
121
 
110
122
  @parameters = refine_params(params)
@@ -119,12 +131,14 @@ module Stomp
119
131
  change_host
120
132
  end
121
133
 
122
- # Syntactic sugar for 'Connection.new' See 'initialize' for usage.
134
+ # open is syntactic sugar for 'Connection.new' See 'initialize' for usage.
123
135
  def Connection.open(login = '', passcode = '', host = 'localhost', port = 61613, reliable = false, reconnect_delay = 5, connect_headers = {})
124
136
  Connection.new(login, passcode, host, port, reliable, reconnect_delay, connect_headers)
125
137
  end
126
138
 
127
- def socket
139
+ # socket creates and returns a new socket for use by the connection.
140
+ # *NOTE* this method will be made private in the next realease.
141
+ def socket()
128
142
  @socket_semaphore.synchronize do
129
143
  used_socket = @socket
130
144
  used_socket = nil if closed?
@@ -132,7 +146,7 @@ module Stomp
132
146
  while used_socket.nil? || !@failure.nil?
133
147
  @failure = nil
134
148
  begin
135
- used_socket = open_socket
149
+ used_socket = open_socket()
136
150
  # Open complete
137
151
 
138
152
  connect(used_socket)
@@ -145,6 +159,7 @@ module Stomp
145
159
  used_socket = nil
146
160
  raise unless @reliable
147
161
  raise if @failure.is_a?(Stomp::Error::LoggerConnectionError)
162
+ @closed = true
148
163
  if @logger && @logger.respond_to?(:on_connectfail)
149
164
  # on_connectfail may raise
150
165
  begin
@@ -162,8 +177,8 @@ module Stomp
162
177
  @connection_attempts += 1
163
178
 
164
179
  if @parameters
165
- change_host
166
- increase_reconnect_delay
180
+ change_host()
181
+ increase_reconnect_delay()
167
182
  end
168
183
  end
169
184
  end
@@ -171,6 +186,8 @@ module Stomp
171
186
  end
172
187
  end
173
188
 
189
+ # refine_params sets up defaults for a Hash initialize.
190
+ # *NOTE* This method will be made private in the next release.
174
191
  def refine_params(params)
175
192
  params = params.uncamelize_and_symbolize_keys
176
193
  default_params = {
@@ -197,6 +214,8 @@ module Stomp
197
214
  return res_params
198
215
  end
199
216
 
217
+ # change_host selects the next host for retires.
218
+ # *NOTE* This method will be made private in the next release.
200
219
  def change_host
201
220
  @parameters[:hosts] = @parameters[:hosts].sort_by { rand } if @parameters[:randomize]
202
221
 
@@ -212,10 +231,16 @@ module Stomp
212
231
 
213
232
  end
214
233
 
234
+ # max_reconnect_attempts? returns nil or the number of maximum reconnect
235
+ # attempts.
236
+ # *NOTE* This method will be made private in the next release.
215
237
  def max_reconnect_attempts?
216
238
  !(@parameters.nil? || @parameters[:max_reconnect_attempts].nil?) && @parameters[:max_reconnect_attempts] != 0 && @connection_attempts >= @parameters[:max_reconnect_attempts]
217
239
  end
218
240
 
241
+ # increase_reconnect_delay increases the reconnect delay for the next connection
242
+ # attempt.
243
+ # *NOTE* This method will be made private in the next release.
219
244
  def increase_reconnect_delay
220
245
 
221
246
  @reconnect_delay *= @parameters[:back_off_multiplier] if @parameters[:use_exponential_back_off]
@@ -224,17 +249,17 @@ module Stomp
224
249
  @reconnect_delay
225
250
  end
226
251
 
227
- # Is this connection open?
252
+ # open? tests if this connection is open.
228
253
  def open?
229
254
  !@closed
230
255
  end
231
256
 
232
- # Is this connection closed?
257
+ # closed? tests if this connection is closed.
233
258
  def closed?
234
259
  @closed
235
260
  end
236
261
 
237
- # Begin a transaction, requires a name for the transaction
262
+ # Begin starts a transaction, and requires a name for the transaction
238
263
  def begin(name, headers = {})
239
264
  raise Stomp::Error::NoCurrentConnection if closed?
240
265
  headers = headers.symbolize_keys
@@ -244,8 +269,7 @@ module Stomp
244
269
  end
245
270
 
246
271
  # Acknowledge a message, used when a subscription has specified
247
- # client acknowledgement ( connection.subscribe "/queue/a", :ack => 'client'g
248
- #
272
+ # client acknowledgement i.e. connection.subscribe("/queue/a", :ack => 'client').
249
273
  # Accepts a transaction header ( :transaction => 'some_transaction_id' )
250
274
  def ack(message_id, headers = {})
251
275
  raise Stomp::Error::NoCurrentConnection if closed?
@@ -259,7 +283,7 @@ module Stomp
259
283
  transmit(Stomp::CMD_ACK, headers)
260
284
  end
261
285
 
262
- # STOMP 1.1+ NACK
286
+ # STOMP 1.1+ NACK.
263
287
  def nack(message_id, headers = {})
264
288
  raise Stomp::Error::NoCurrentConnection if closed?
265
289
  raise Stomp::Error::UnsupportedProtocolError if @protocol == Stomp::SPL_10
@@ -271,7 +295,7 @@ module Stomp
271
295
  transmit(Stomp::CMD_NACK, headers)
272
296
  end
273
297
 
274
- # Commit a transaction by name
298
+ # Commit commits a transaction by name.
275
299
  def commit(name, headers = {})
276
300
  raise Stomp::Error::NoCurrentConnection if closed?
277
301
  headers = headers.symbolize_keys
@@ -280,7 +304,7 @@ module Stomp
280
304
  transmit(Stomp::CMD_COMMIT, headers)
281
305
  end
282
306
 
283
- # Abort a transaction by name
307
+ # Abort aborts a transaction by name.
284
308
  def abort(name, headers = {})
285
309
  raise Stomp::Error::NoCurrentConnection if closed?
286
310
  headers = headers.symbolize_keys
@@ -289,7 +313,8 @@ module Stomp
289
313
  transmit(Stomp::CMD_ABORT, headers)
290
314
  end
291
315
 
292
- # Subscribe to a destination, must specify a name
316
+ # Subscribe subscribes to a destination. A subscription name is required.
317
+ # For Stomp 1.1 a session unique subscription ID is required.
293
318
  def subscribe(name, headers = {}, subId = nil)
294
319
  raise Stomp::Error::NoCurrentConnection if closed?
295
320
  headers = headers.symbolize_keys
@@ -313,7 +338,8 @@ module Stomp
313
338
  transmit(Stomp::CMD_SUBSCRIBE, headers)
314
339
  end
315
340
 
316
- # Unsubscribe from a destination, which must be specified
341
+ # Unsubscribe from a destination. A subscription name is required.
342
+ # For Stomp 1.1 a session unique subscription ID is required.
317
343
  def unsubscribe(dest, headers = {}, subId = nil)
318
344
  raise Stomp::Error::NoCurrentConnection if closed?
319
345
  headers = headers.symbolize_keys
@@ -329,10 +355,9 @@ module Stomp
329
355
  end
330
356
  end
331
357
 
332
- # Publish message to destination
333
- #
334
- # To disable content length header ( :suppress_content_length => true )
335
- # Accepts a transaction header ( :transaction => 'some_transaction_id' )
358
+ # Publish message to destination.
359
+ # To disable content length header use header ( :suppress_content_length => true ).
360
+ # Accepts a transaction header ( :transaction => 'some_transaction_id' ).
336
361
  def publish(destination, message, headers = {})
337
362
  raise Stomp::Error::NoCurrentConnection if closed?
338
363
  headers = headers.symbolize_keys
@@ -344,18 +369,19 @@ module Stomp
344
369
  transmit(Stomp::CMD_SEND, headers, message)
345
370
  end
346
371
 
372
+ # :TODO: Remove this method.
373
+ # *NOTE* This method will be removed in the next release.
347
374
  def obj_send(*args)
348
375
  __send__(*args)
349
376
  end
350
377
 
351
- # Send a message back to the source or to the dead letter queue
352
- #
353
- # Accepts a dead letter queue option ( :dead_letter_queue => "/queue/DLQ" )
354
- # Accepts a limit number of redeliveries option ( :max_redeliveries => 6 )
355
- # Accepts a force client acknowledgement option (:force_client_ack => true)
378
+ # Send a message back to the source or to the dead letter queue.
379
+ # Accepts a dead letter queue option ( :dead_letter_queue => "/queue/DLQ" ).
380
+ # Accepts a limit number of redeliveries option ( :max_redeliveries => 6 ).
381
+ # Accepts a force client acknowledgement option (:force_client_ack => true).
356
382
  def unreceive(message, options = {})
357
383
  raise Stomp::Error::NoCurrentConnection if closed?
358
- options = { :dead_letter_queue => "/queue/DLQ", :max_redeliveries => 6 }.merge options
384
+ options = { :dead_letter_queue => "/queue/DLQ", :max_redeliveries => 6 }.merge(options)
359
385
  # Lets make sure all keys are symbols
360
386
  message.headers = message.headers.symbolize_keys
361
387
 
@@ -372,10 +398,14 @@ module Stomp
372
398
  end
373
399
 
374
400
  if retry_count <= options[:max_redeliveries]
375
- self.publish(message.headers[:destination], message.body, message.headers.merge(:transaction => transaction_id))
401
+ self.publish(message.headers[:destination], message.body,
402
+ message.headers.merge(:transaction => transaction_id))
376
403
  else
377
404
  # Poison ack, sending the message to the DLQ
378
- self.publish(options[:dead_letter_queue], message.body, message.headers.merge(:transaction => transaction_id, :original_destination => message.headers[:destination], :persistent => true))
405
+ self.publish(options[:dead_letter_queue], message.body,
406
+ message.headers.merge(:transaction => transaction_id,
407
+ :original_destination => message.headers[:destination],
408
+ :persistent => true))
379
409
  end
380
410
  self.commit transaction_id
381
411
  rescue Exception => exception
@@ -384,12 +414,14 @@ module Stomp
384
414
  end
385
415
  end
386
416
 
417
+ # client_ack? determines if headers contain :ack => "client".
387
418
  def client_ack?(message)
388
419
  headers = @subscriptions[message.headers[:destination]]
389
420
  !headers.nil? && headers[:ack] == "client"
390
421
  end
391
422
 
392
- # Close this connection
423
+ # disconnect closes this connection. If requested, a disconnect RECEIPT
424
+ # is received.
393
425
  def disconnect(headers = {})
394
426
  raise Stomp::Error::NoCurrentConnection if closed?
395
427
  headers = headers.symbolize_keys
@@ -406,18 +438,19 @@ module Stomp
406
438
  close_socket
407
439
  end
408
440
 
409
- # Return a pending message if one is available, otherwise
410
- # return nil
411
- def poll
441
+ # poll returns a pending message if one is available, otherwise
442
+ # returns nil.
443
+ def poll()
412
444
  raise Stomp::Error::NoCurrentConnection if closed?
413
445
  # No need for a read lock here. The receive method eventually fulfills
414
446
  # that requirement.
415
447
  return nil if @socket.nil? || !@socket.ready?
416
- receive
448
+ receive()
417
449
  end
418
450
 
419
- # Receive a frame, block until the frame is received
420
- def __old_receive
451
+ # __old_receive receives a frame, blocks until the frame is received.
452
+ # *NOTE* This method will be made private in the next release.
453
+ def __old_receive()
421
454
  # The receive may fail so we may need to retry.
422
455
  while TRUE
423
456
  begin
@@ -436,7 +469,8 @@ module Stomp
436
469
  end
437
470
  end
438
471
 
439
- def receive
472
+ # receive returns the next Message off of the wire.
473
+ def receive()
440
474
  raise Stomp::Error::NoCurrentConnection if closed?
441
475
  super_result = __old_receive
442
476
  if super_result.nil? && @reliable && !closed?
@@ -456,855 +490,65 @@ module Stomp
456
490
  return super_result
457
491
  end
458
492
 
459
- # Convenience method
493
+ # set_logger selects a new callback logger instance.
460
494
  def set_logger(logger)
461
495
  @logger = logger
462
496
  end
463
497
 
464
- # Convenience method
498
+ # valid_utf8? returns an indicator if the given string is a valid UTF8 string.
465
499
  def valid_utf8?(s)
466
500
  case RUBY_VERSION
467
- when /1\.8/
468
- rv = _valid_utf8?(s)
469
- else
470
- rv = s.encoding.name != Stomp::UTF8 ? false : s.valid_encoding?
501
+ when /1\.8/
502
+ rv = _valid_utf8?(s)
503
+ else
504
+ rv = s.encoding.name != Stomp::UTF8 ? false : s.valid_encoding?
471
505
  end
472
506
  rv
473
507
  end
474
508
 
475
- # Convenience method for clients, return a SHA1 digest for arbitrary data
509
+ # sha1 returns a SHA1 digest for arbitrary string data.
476
510
  def sha1(data)
477
511
  Digest::SHA1.hexdigest(data)
478
512
  end
479
513
 
480
- # Convenience method for clients, return a type 4 UUID.
514
+ # uuid returns a type 4 UUID.
481
515
  def uuid()
482
516
  b = []
483
517
  0.upto(15) do |i|
484
518
  b << rand(255)
485
519
  end
486
- b[6] = (b[6] & 0x0F) | 0x40
487
- b[8] = (b[8] & 0xbf) | 0x80
520
+ b[6] = (b[6] & 0x0F) | 0x40
521
+ b[8] = (b[8] & 0xbf) | 0x80
488
522
  # 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
489
- rs = sprintf("%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x%02x%02x",
490
- 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])
523
+ rs = sprintf("%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x%02x%02x",
524
+ 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])
491
525
  rs
492
526
  end
493
527
 
494
- # Retrieve heartbeat send interval
495
- def hbsend_interval
528
+ # hbsend_interval returns the connection's heartbeat send interval.
529
+ def hbsend_interval()
496
530
  return 0 unless @hbsend_interval
497
531
  @hbsend_interval / 1000.0 # ms
498
532
  end
499
533
 
500
- # Retrieve heartbeat receive interval
501
- def hbrecv_interval
534
+ # hbrecv_interval returns the connection's heartbeat receive interval.
535
+ def hbrecv_interval()
502
536
  return 0 unless @hbrecv_interval
503
537
  @hbrecv_interval / 1000.0 # ms
504
538
  end
505
539
 
506
- # Retrieve heartbeat send count
507
- def hbsend_count
540
+ # hbsend_count returns the current connection's heartbeat send count.
541
+ def hbsend_count()
508
542
  return 0 unless @hbsend_count
509
543
  @hbsend_count
510
544
  end
511
545
 
512
- # Retrieve heartbeat receive count
513
- def hbrecv_count
546
+ # hbrecv_count returns the current connection's heartbeat receive count.
547
+ def hbrecv_count()
514
548
  return 0 unless @hbrecv_count
515
549
  @hbrecv_count
516
550
  end
517
551
 
518
- private
519
-
520
- def _expand_hosts(hash)
521
- new_hash = hash.clone
522
- new_hash[:hosts_cloned] = hash[:hosts].clone
523
- new_hash[:hosts] = []
524
- #
525
- hash[:hosts].each do |host_parms|
526
- ai = Socket.getaddrinfo(host_parms[:host], nil, nil, Socket::SOCK_STREAM)
527
- next if ai.nil? || ai.size == 0
528
- info6 = ai.detect {|info| info[4] == Socket::AF_INET6}
529
- info4 = ai.detect {|info| info[4] == Socket::AF_INET}
530
- if info6
531
- new_hostp = host_parms.clone
532
- new_hostp[:host] = info6[3]
533
- new_hash[:hosts] << new_hostp
534
- end
535
- if info4
536
- new_hostp = host_parms.clone
537
- new_hostp[:host] = info4[3]
538
- new_hash[:hosts] << new_hostp
539
- end
540
- end
541
- return new_hash
542
- end
543
-
544
- def _receive( read_socket )
545
- @read_semaphore.synchronize do
546
- line = ''
547
- if @protocol == Stomp::SPL_10 || (@protocol >= Stomp::SPL_11 && !@hbr)
548
- line = read_socket.gets # The old way
549
- else # We are >= 1.1 and receiving heartbeats.
550
- while true
551
- line = read_socket.gets # Data from wire
552
- break unless line == "\n"
553
- line = ''
554
- @lr = Time.now.to_f
555
- end
556
- end
557
- return nil if line.nil?
558
- # If the reading hangs for more than X seconds, abort the parsing process.
559
- # X defaults to 5. Override allowed in connection hash parameters.
560
- Timeout::timeout(@parse_timeout, Stomp::Error::PacketParsingTimeout) do
561
- # Reads the beginning of the message until it runs into a empty line
562
- message_header = ''
563
- begin
564
- message_header += line
565
- line = read_socket.gets
566
- raise Stomp::Error::StompServerError if line.nil?
567
- end until line =~ /^\s?\n$/
568
-
569
- # Checks if it includes content_length header
570
- content_length = message_header.match /content-length\s?:\s?(\d+)\s?\n/
571
- message_body = ''
572
-
573
- # If content_length is present, read the specified amount of bytes
574
- if content_length
575
- message_body = read_socket.read content_length[1].to_i
576
- raise Stomp::Error::InvalidMessageLength unless parse_char(read_socket.getc) == "\0"
577
- # Else read the rest of the message until the first \0
578
- else
579
- message_body = read_socket.readline("\0")
580
- message_body.chop!
581
- end
582
-
583
- # If the buffer isn't empty, reads trailing new lines.
584
- #
585
- # Note: experiments with JRuby seem to show that .ready? never
586
- # returns true. This means that this code to drain trailing new
587
- # lines never runs using JRuby.
588
- #
589
- # Note 2: the draining of new lines mmust be done _after_ a message
590
- # is read. Do _not_ leave them on the wire and attempt to drain them
591
- # at the start of the next read. Attempting to do that breaks the
592
- # asynchronous nature of the 'poll' method.
593
- while read_socket.ready?
594
- last_char = read_socket.getc
595
- break unless last_char
596
- if parse_char(last_char) != "\n"
597
- read_socket.ungetc(last_char)
598
- break
599
- end
600
- end
601
- # And so, a JRuby hack. Remove any new lines at the start of the
602
- # next buffer.
603
- message_header.gsub!(/^\n?/, "")
604
-
605
- if @protocol >= Stomp::SPL_11
606
- @lr = Time.now.to_f if @hbr
607
- end
608
- # Adds the excluded \n and \0 and tries to create a new message with it
609
- msg = Message.new(message_header + "\n" + message_body + "\0", @protocol >= Stomp::SPL_11)
610
- #
611
- if @protocol >= Stomp::SPL_11 && msg.command != Stomp::CMD_CONNECTED
612
- msg.headers = _decodeHeaders(msg.headers)
613
- end
614
- msg
615
- end
616
- end
617
- end
618
-
619
- def parse_char(char)
620
- RUBY_VERSION > '1.9' ? char : char.chr
621
- end
622
-
623
- def transmit(command, headers = {}, body = '')
624
- # The transmit may fail so we may need to retry.
625
- while TRUE
626
- begin
627
- used_socket = socket
628
- _transmit(used_socket, command, headers, body)
629
- return
630
- rescue Stomp::Error::MaxReconnectAttempts => e
631
- raise
632
- rescue
633
- @failure = $!
634
- raise unless @reliable
635
- errstr = "transmit to #{@host} failed: #{$!}\n"
636
- if @logger && @logger.respond_to?(:on_miscerr)
637
- @logger.on_miscerr(log_params, errstr)
638
- else
639
- $stderr.print errstr
640
- end
641
- end
642
- end
643
- end
644
-
645
- def _transmit(used_socket, command, headers = {}, body = '')
646
- if @protocol >= Stomp::SPL_11 && command != Stomp::CMD_CONNECT
647
- headers = _encodeHeaders(headers)
648
- end
649
- @transmit_semaphore.synchronize do
650
- # Handle nil body
651
- body = '' if body.nil?
652
- # The content-length should be expressed in bytes.
653
- # Ruby 1.8: String#length => # of bytes; Ruby 1.9: String#length => # of characters
654
- # With Unicode strings, # of bytes != # of characters. So, use String#bytesize when available.
655
- body_length_bytes = body.respond_to?(:bytesize) ? body.bytesize : body.length
656
-
657
- # ActiveMQ interprets every message as a BinaryMessage
658
- # if content_length header is included.
659
- # Using :suppress_content_length => true will suppress this behaviour
660
- # and ActiveMQ will interpret the message as a TextMessage.
661
- # For more information refer to http://juretta.com/log/2009/05/24/activemq-jms-stomp/
662
- # Lets send this header in the message, so it can maintain state when using unreceive
663
- headers[:'content-length'] = "#{body_length_bytes}" unless headers[:suppress_content_length]
664
- headers[:'content-type'] = "text/plain; charset=UTF-8" unless headers[:'content-type']
665
- used_socket.puts command
666
- headers.each do |k,v|
667
- if v.is_a?(Array)
668
- v.each do |e|
669
- used_socket.puts "#{k}:#{e}"
670
- end
671
- else
672
- used_socket.puts "#{k}:#{v}"
673
- end
674
- end
675
- used_socket.puts
676
- used_socket.write body
677
- used_socket.write "\0"
678
- used_socket.flush if autoflush
679
-
680
- if @protocol >= Stomp::SPL_11
681
- @ls = Time.now.to_f if @hbs
682
- end
683
-
684
- end
685
- end
686
-
687
- def open_tcp_socket
688
- tcp_socket = nil
689
-
690
- if @logger && @logger.respond_to?(:on_connecting)
691
- @logger.on_connecting(log_params)
692
- end
693
-
694
- Timeout::timeout(@connect_timeout, Stomp::Error::SocketOpenTimeout) do
695
- tcp_socket = TCPSocket.open @host, @port
696
- end
697
-
698
- tcp_socket
699
- end
700
-
701
- def open_ssl_socket
702
- require 'openssl' unless defined?(OpenSSL)
703
- begin # Any raised SSL exceptions
704
- ctx = OpenSSL::SSL::SSLContext.new
705
- ctx.verify_mode = OpenSSL::SSL::VERIFY_NONE # Assume for now
706
- #
707
- # Note: if a client uses :ssl => true this results in the gem using
708
- # the _default_ Ruby ciphers list. This is _known_ to fail in later
709
- # Ruby releases. The gem provides a default cipher list that may
710
- # function in these cases. To use this connect with:
711
- # * :ssl => Stomp::SSLParams.new
712
- # * :ssl => Stomp::SSLParams.new(..., :ciphers => Stomp::DEFAULT_CIPHERS)
713
- #
714
- # If connecting with an SSLParams instance, and the _default_ Ruby
715
- # ciphers list is required, use:
716
- # * :ssl => Stomp::SSLParams.new(..., :use_ruby_ciphers => true)
717
- #
718
- # If a custom ciphers list is required, connect with:
719
- # * :ssl => Stomp::SSLParams.new(..., :ciphers => custom_ciphers_list)
720
- #
721
- if @ssl != true
722
- #
723
- # Here @ssl is:
724
- # * an instance of Stomp::SSLParams
725
- # Control would not be here if @ssl == false or @ssl.nil?.
726
- #
727
-
728
- # Back reference the SSLContext
729
- @ssl.ctx = ctx
730
-
731
- # Server authentication parameters if required
732
- if @ssl.ts_files
733
- ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER
734
- truststores = OpenSSL::X509::Store.new
735
- fl = @ssl.ts_files.split(",")
736
- fl.each do |fn|
737
- # Add next cert file listed
738
- raise Stomp::Error::SSLNoTruststoreFileError if !File::exists?(fn)
739
- raise Stomp::Error::SSLUnreadableTruststoreFileError if !File::readable?(fn)
740
- truststores.add_file(fn)
741
- end
742
- ctx.cert_store = truststores
743
- end
744
-
745
- # Client authentication parameters
746
- # Both cert file and key file must be present or not, it can not be a mix
747
- raise Stomp::Error::SSLClientParamsError if @ssl.cert_file.nil? && !@ssl.key_file.nil?
748
- raise Stomp::Error::SSLClientParamsError if !@ssl.cert_file.nil? && @ssl.key_file.nil?
749
- if @ssl.cert_file # Any check will do here
750
- raise Stomp::Error::SSLNoCertFileError if !File::exists?(@ssl.cert_file)
751
- raise Stomp::Error::SSLUnreadableCertFileError if !File::readable?(@ssl.cert_file)
752
- ctx.cert = OpenSSL::X509::Certificate.new(File.read(@ssl.cert_file))
753
- raise Stomp::Error::SSLNoKeyFileError if !File::exists?(@ssl.key_file)
754
- raise Stomp::Error::SSLUnreadableKeyFileError if !File::readable?(@ssl.key_file)
755
- ctx.key = OpenSSL::PKey::RSA.new(File.read(@ssl.key_file), @ssl.key_password)
756
- end
757
-
758
- # Cipher list
759
- if !@ssl.use_ruby_ciphers # No Ruby ciphers (the default)
760
- if @ssl.ciphers # User ciphers list?
761
- ctx.ciphers = @ssl.ciphers # Accept user supplied ciphers
762
- else
763
- ctx.ciphers = Stomp::DEFAULT_CIPHERS # Just use Stomp defaults
764
- end
765
- end
766
- end
767
-
768
- #
769
- ssl = nil
770
- if @logger && @logger.respond_to?(:on_ssl_connecting)
771
- @logger.on_ssl_connecting(log_params)
772
- end
773
-
774
- Timeout::timeout(@connect_timeout, Stomp::Error::SocketOpenTimeout) do
775
- ssl = OpenSSL::SSL::SSLSocket.new(open_tcp_socket, ctx)
776
- end
777
- def ssl.ready?
778
- ! @rbuffer.empty? || @io.ready?
779
- end
780
- ssl.connect
781
- if @ssl != true
782
- # Pass back results if possible
783
- if RUBY_VERSION =~ /1\.8\.[56]/
784
- @ssl.verify_result = "N/A for Ruby #{RUBY_VERSION}"
785
- else
786
- @ssl.verify_result = ssl.verify_result
787
- end
788
- @ssl.peer_cert = ssl.peer_cert
789
- end
790
- if @logger && @logger.respond_to?(:on_ssl_connected)
791
- @logger.on_ssl_connected(log_params)
792
- end
793
- ssl
794
- rescue Exception => ex
795
- if @logger && @logger.respond_to?(:on_ssl_connectfail)
796
- lp = log_params.clone
797
- lp[:ssl_exception] = ex
798
- @logger.on_ssl_connectfail(lp)
799
- end
800
- #
801
- raise # Reraise
802
- end
803
- end
804
-
805
- def close_socket
806
- begin
807
- # Need to set @closed = true before closing the socket
808
- # within the @read_semaphore thread
809
- @closed = true
810
- @read_semaphore.synchronize do
811
- @socket.close
812
- end
813
- rescue
814
- #Ignoring if already closed
815
- end
816
- @closed
817
- end
818
-
819
- def open_socket
820
- used_socket = @ssl ? open_ssl_socket : open_tcp_socket
821
- # try to close the old connection if any
822
- close_socket
823
-
824
- @closed = false
825
- # Use keepalive
826
- used_socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, true)
827
- used_socket
828
- end
829
-
830
- def connect(used_socket)
831
- @connect_headers = {} unless @connect_headers # Caller said nil/false
832
- headers = @connect_headers.clone
833
- headers[:login] = @login
834
- headers[:passcode] = @passcode
835
- _pre_connect
836
- _transmit(used_socket, "CONNECT", headers)
837
- @connection_frame = _receive(used_socket)
838
- _post_connect
839
- @disconnect_receipt = nil
840
- @session = @connection_frame.headers["session"] if @connection_frame
841
- # replay any subscriptions.
842
- @subscriptions.each { |k,v| _transmit(used_socket, Stomp::CMD_SUBSCRIBE, v) }
843
- end
844
-
845
- def log_params
846
- lparms = @parameters.clone if @parameters
847
- lparms = {} unless lparms
848
- lparms[:cur_host] = @host
849
- lparms[:cur_port] = @port
850
- lparms[:cur_login] = @login
851
- lparms[:cur_passcode] = @passcode
852
- lparms[:cur_ssl] = @ssl
853
- lparms[:cur_recondelay] = @reconnect_delay
854
- lparms[:cur_parseto] = @parse_timeout
855
- lparms[:cur_conattempts] = @connection_attempts
856
- #
857
- lparms
858
- end
859
-
860
- def _pre_connect
861
- @connect_headers = @connect_headers.symbolize_keys
862
- raise Stomp::Error::ProtocolErrorConnect if (@connect_headers[:"accept-version"] && !@connect_headers[:host])
863
- raise Stomp::Error::ProtocolErrorConnect if (!@connect_headers[:"accept-version"] && @connect_headers[:host])
864
- return unless (@connect_headers[:"accept-version"] && @connect_headers[:host]) # 1.0
865
- # Try 1.1 or greater
866
- okvers = []
867
- avers = @connect_headers[:"accept-version"].split(",")
868
- avers.each do |nver|
869
- if Stomp::SUPPORTED.index(nver)
870
- okvers << nver
871
- end
872
- end
873
- raise Stomp::Error::UnsupportedProtocolError if okvers == []
874
- @connect_headers[:"accept-version"] = okvers.join(",") # This goes to server
875
- # Heartbeats - pre connect
876
- return unless @connect_headers[:"heart-beat"]
877
- _validate_hbheader()
878
- end
879
-
880
- def _post_connect
881
- return unless (@connect_headers[:"accept-version"] && @connect_headers[:host])
882
- return if @connection_frame.command == Stomp::CMD_ERROR
883
- # We are CONNECTed
884
- cfh = @connection_frame.headers.symbolize_keys
885
- @protocol = cfh[:version]
886
- if @protocol
887
- # Should not happen, but check anyway
888
- raise Stomp::Error::UnsupportedProtocolError unless Stomp::SUPPORTED.index(@protocol)
889
- else # CONNECTed to a 1.0 server that does not return *any* 1.1 type headers
890
- @protocol = Stomp::SPL_10 # reset
891
- return
892
- end
893
- # Heartbeats
894
- return unless @connect_headers[:"heart-beat"]
895
- _init_heartbeats()
896
- end
897
-
898
- def _validate_hbheader()
899
- return if @connect_headers[:"heart-beat"] == "0,0" # Caller does not want heartbeats. OK.
900
- parts = @connect_headers[:"heart-beat"].split(",")
901
- if (parts.size != 2) || (parts[0] != parts[0].to_i.to_s) || (parts[1] != parts[1].to_i.to_s)
902
- raise Stomp::Error::InvalidHeartBeatHeaderError
903
- end
904
- end
905
-
906
- def _init_heartbeats()
907
- return if @connect_headers[:"heart-beat"] == "0,0" # Caller does not want heartbeats. OK.
908
- #
909
- @cx = @cy = @sx = @sy = 0, # Variable names as in spec
910
- #
911
- @hbsend_interval = @hbrecv_interval = 0.0 # Send/Receive ticker interval.
912
- #
913
- @hbsend_count = @hbrecv_count = 0 # Send/Receive ticker counts.
914
- #
915
- @ls = @lr = -1.0 # Last send/receive time (from Time.now.to_f)
916
- #
917
- @st = @rt = nil # Send/receive ticker thread
918
- #
919
- cfh = @connection_frame.headers.symbolize_keys
920
- return if cfh[:"heart-beat"] == "0,0" # Server does not want heartbeats
921
- #
922
- parts = @connect_headers[:"heart-beat"].split(",")
923
- @cx = parts[0].to_i
924
- @cy = parts[1].to_i
925
- #
926
- parts = cfh[:"heart-beat"].split(",")
927
- @sx = parts[0].to_i
928
- @sy = parts[1].to_i
929
- # Catch odd situations like someone has used => heart-beat:000,00000
930
- return if (@cx == 0 && @cy == 0) || (@sx == 0 && @sy == 0)
931
- #
932
- @hbs = @hbr = true # Sending/Receiving heartbeats. Assume yes at first.
933
- # Check for sending
934
- @hbs = false if @cx == 0 || @sy == 0
935
- # Check for receiving
936
- @hbr = false if @sx == 0 || @cy == 0
937
- # Should not do heartbeats at all
938
- return if (!@hbs && !@hbr)
939
- # If sending
940
- if @hbs
941
- sm = @cx >= @sy ? @cx : @sy # ticker interval, ms
942
- @hbsend_interval = 1000.0 * sm # ticker interval, μs
943
- @ls = Time.now.to_f # best guess at start
944
- _start_send_ticker
945
- end
946
-
947
- # If receiving
948
- if @hbr
949
- rm = @sx >= @cy ? @sx : @cy # ticker interval, ms
950
- @hbrecv_interval = 1000.0 * rm # ticker interval, μs
951
- @lr = Time.now.to_f # best guess at start
952
- _start_receive_ticker
953
- end
954
-
955
- end
956
-
957
- def _start_send_ticker
958
- sleeptime = @hbsend_interval / 1000000.0 # Sleep time secs
959
- @st = Thread.new {
960
- while true do
961
- sleep sleeptime
962
- curt = Time.now.to_f
963
- if @logger && @logger.respond_to?(:on_hbfire)
964
- @logger.on_hbfire(log_params, "send_fire", curt)
965
- end
966
- delta = curt - @ls
967
- if delta > (@hbsend_interval - (@hbsend_interval/5.0)) / 1000000.0 # Be tolerant (minus)
968
- if @logger && @logger.respond_to?(:on_hbfire)
969
- @logger.on_hbfire(log_params, "send_heartbeat", curt)
970
- end
971
- # Send a heartbeat
972
- @transmit_semaphore.synchronize do
973
- begin
974
- @socket.puts
975
- @ls = curt # Update last send
976
- @hb_sent = true # Reset if necessary
977
- @hbsend_count += 1
978
- rescue Exception => sendex
979
- @hb_sent = false # Set the warning flag
980
- if @logger && @logger.respond_to?(:on_hbwrite_fail)
981
- @logger.on_hbwrite_fail(log_params, {"ticker_interval" => @hbsend_interval,
982
- "exception" => sendex})
983
- end
984
- raise # Re-raise. What else could be done here?
985
- end
986
- end
987
- end
988
- Thread.pass
989
- end
990
- }
991
- end
992
-
993
- def _start_receive_ticker
994
- sleeptime = @hbrecv_interval / 1000000.0 # Sleep time secs
995
- @rt = Thread.new {
996
- while true do
997
- sleep sleeptime
998
- curt = Time.now.to_f
999
- if @logger && @logger.respond_to?(:on_hbfire)
1000
- @logger.on_hbfire(log_params, "receive_fire", curt)
1001
- end
1002
- delta = curt - @lr
1003
- if delta > ((@hbrecv_interval + (@hbrecv_interval/5.0)) / 1000000.0) # Be tolerant (plus)
1004
- if @logger && @logger.respond_to?(:on_hbfire)
1005
- @logger.on_hbfire(log_params, "receive_heartbeat", curt)
1006
- end
1007
- # Client code could be off doing something else (that is, no reading of
1008
- # the socket has been requested by the caller). Try to handle that case.
1009
- lock = @read_semaphore.try_lock
1010
- if lock
1011
- last_char = @socket.getc
1012
- plc = parse_char(last_char)
1013
- if plc == "\n" # Server Heartbeat
1014
- @lr = Time.now.to_f
1015
- else
1016
- @socket.ungetc(last_char)
1017
- end
1018
- @read_semaphore.unlock
1019
- @hbrecv_count += 1
1020
- else
1021
- # Shrug. Have not received one. Just set warning flag.
1022
- @hb_received = false
1023
- if @logger && @logger.respond_to?(:on_hbread_fail)
1024
- @logger.on_hbread_fail(log_params, {"ticker_interval" => @hbrecv_interval})
1025
- end
1026
- end
1027
- else
1028
- @hb_received = true # Reset if necessary
1029
- end
1030
- Thread.pass
1031
- end
1032
- }
1033
- end
1034
-
1035
- # Ref:
1036
- # http://unicode.org/mail-arch/unicode-ml/y2003-m02/att-0467/01-The_Algorithm_to_Valide_an_UTF-8_String
1037
- #
1038
- def _valid_utf8?(string)
1039
- case RUBY_VERSION
1040
- when /1\.8\.[56]/
1041
- bytes = []
1042
- 0.upto(string.length-1) {|i|
1043
- bytes << string[i]
1044
- }
1045
- else
1046
- bytes = string.bytes
1047
- end
1048
-
1049
- #
1050
- valid = true
1051
- index = -1
1052
- nb_hex = nil
1053
- ni_hex = nil
1054
- state = "start"
1055
- next_byte_save = nil
1056
- #
1057
- bytes.each do |next_byte|
1058
- index += 1
1059
- next_byte_save = next_byte
1060
- ni_hex = sprintf "%x", index
1061
- nb_hex = sprintf "%x", next_byte
1062
- # puts "Top: #{next_byte}(0x#{nb_hex}), index: #{index}(0x#{ni_hex})" if DEBUG
1063
- case state
1064
-
1065
- # State: 'start'
1066
- # The 'start' state:
1067
- # * handles all occurrences of valid single byte characters i.e., the ASCII character set
1068
- # * provides state transition logic for start bytes of valid characters with 2-4 bytes
1069
- # * signals a validation failure for all other single bytes
1070
- #
1071
- when "start"
1072
- # puts "state: start" if DEBUG
1073
- case next_byte
1074
-
1075
- # ASCII
1076
- # * Input = 0x00-0x7F : change state to START
1077
- when (0x00..0x7f)
1078
- # puts "state: start 1" if DEBUG
1079
- state = "start"
1080
-
1081
- # Start byte of two byte characters
1082
- # * Input = 0xC2-0xDF: change state to A
1083
- when (0xc2..0xdf)
1084
- # puts "state: start 2" if DEBUG
1085
- state = "a"
1086
-
1087
- # Start byte of some three byte characters
1088
- # * Input = 0xE1-0xEC, 0xEE-0xEF: change state to B
1089
- when (0xe1..0xec)
1090
- # puts "state: start 3" if DEBUG
1091
- state = "b"
1092
- when (0xee..0xef)
1093
- # puts "state: start 4" if DEBUG
1094
- state = "b"
1095
-
1096
- # Start byte of special three byte characters
1097
- # * Input = 0xE0: change state to C
1098
- when 0xe0
1099
- # puts "state: start 5" if DEBUG
1100
- state = "c"
1101
-
1102
- # Start byte of the remaining three byte characters
1103
- # * Input = 0xED: change state to D
1104
- when 0xed
1105
- # puts "state: start 6" if DEBUG
1106
- state = "d"
1107
-
1108
- # Start byte of some four byte characters
1109
- # * Input = 0xF1-0xF3:change state to E
1110
- when (0xf1..0xf3)
1111
- # puts "state: start 7" if DEBUG
1112
- state = "e"
1113
-
1114
- # Start byte of special four byte characters
1115
- # * Input = 0xF0: change state to F
1116
- when 0xf0
1117
- # puts "state: start 8" if DEBUG
1118
- state = "f"
1119
-
1120
- # Start byte of very special four byte characters
1121
- # * Input = 0xF4: change state to G
1122
- when 0xf4
1123
- # puts "state: start 9" if DEBUG
1124
- state = "g"
1125
-
1126
- # All other single characters are invalid
1127
- # * Input = Others (0x80-0xBF,0xC0-0xC1, 0xF5-0xFF): ERROR
1128
- else
1129
- valid = false
1130
- break
1131
- end # of the inner case, the 'start' state
1132
-
1133
- # The last continuation byte of a 2, 3, or 4 byte character
1134
- # State: 'a'
1135
- # o Input = 0x80-0xBF: change state to START
1136
- # o Others: ERROR
1137
- when "a"
1138
- # puts "state: a" if DEBUG
1139
- if (0x80..0xbf) === next_byte
1140
- state = "start"
1141
- else
1142
- valid = false
1143
- break
1144
- end
1145
-
1146
- # The first continuation byte for most 3 byte characters
1147
- # (those with start bytes in: 0xe1-0xec or 0xee-0xef)
1148
- # State: 'b'
1149
- # o Input = 0x80-0xBF: change state to A
1150
- # o Others: ERROR
1151
- when "b"
1152
- # puts "state: b" if DEBUG
1153
- if (0x80..0xbf) === next_byte
1154
- state = "a"
1155
- else
1156
- valid = false
1157
- break
1158
- end
1159
-
1160
- # The first continuation byte for some special 3 byte characters
1161
- # (those with start byte 0xe0)
1162
- # State: 'c'
1163
- # o Input = 0xA0-0xBF: change state to A
1164
- # o Others: ERROR
1165
- when "c"
1166
- # puts "state: c" if DEBUG
1167
- if (0xa0..0xbf) === next_byte
1168
- state = "a"
1169
- else
1170
- valid = false
1171
- break
1172
- end
1173
-
1174
- # The first continuation byte for the remaining 3 byte characters
1175
- # (those with start byte 0xed)
1176
- # State: 'd'
1177
- # o Input = 0x80-0x9F: change state to A
1178
- # o Others: ERROR
1179
- when "d"
1180
- # puts "state: d" if DEBUG
1181
- if (0x80..0x9f) === next_byte
1182
- state = "a"
1183
- else
1184
- valid = false
1185
- break
1186
- end
1187
-
1188
- # The first continuation byte for some 4 byte characters
1189
- # (those with start bytes in: 0xf1-0xf3)
1190
- # State: 'e'
1191
- # o Input = 0x80-0xBF: change state to B
1192
- # o Others: ERROR
1193
- when "e"
1194
- # puts "state: e" if DEBUG
1195
- if (0x80..0xbf) === next_byte
1196
- state = "b"
1197
- else
1198
- valid = false
1199
- break
1200
- end
1201
-
1202
- # The first continuation byte for some special 4 byte characters
1203
- # (those with start byte 0xf0)
1204
- # State: 'f'
1205
- # o Input = 0x90-0xBF: change state to B
1206
- # o Others: ERROR
1207
- when "f"
1208
- # puts "state: f" if DEBUG
1209
- if (0x90..0xbf) === next_byte
1210
- state = "b"
1211
- else
1212
- valid = false
1213
- break
1214
- end
1215
-
1216
- # The first continuation byte for the remaining 4 byte characters
1217
- # (those with start byte 0xf4)
1218
- # State: 'g'
1219
- # o Input = 0x80-0x8F: change state to B
1220
- # o Others: ERROR
1221
- when "g"
1222
- # puts "state: g" if DEBUG
1223
- if (0x80..0x8f) === next_byte
1224
- state = "b"
1225
- else
1226
- valid = false
1227
- break
1228
- end
1229
-
1230
- #
1231
- else
1232
- raise RuntimeError, "state: default"
1233
- end
1234
- end
1235
- #
1236
- # puts "State at end: #{state}" if DEBUG
1237
- # Catch truncation at end of string
1238
- if valid and state != 'start'
1239
- # puts "Resetting valid value" if DEBUG
1240
- valid = false
1241
- end
1242
- #
1243
- valid
1244
- end # of _valid_utf8?
1245
-
1246
- def _headerCheck(h)
1247
- return if @protocol == Stomp::SPL_10 # Do nothing for this environment
1248
- #
1249
- h.each_pair do |k,v|
1250
- # Keys here are symbolized
1251
- ks = k.to_s
1252
- ks.force_encoding(Stomp::UTF8) if ks.respond_to?(:force_encoding)
1253
- raise Stomp::Error::UTF8ValidationError unless valid_utf8?(ks)
1254
- #
1255
- if v.is_a?(Array)
1256
- v.each do |e|
1257
- e.force_encoding(Stomp::UTF8) if e.respond_to?(:force_encoding)
1258
- raise Stomp::Error::UTF8ValidationError unless valid_utf8?(e)
1259
- end
1260
- else
1261
- vs = v.to_s + "" # Values are usually Strings, but could be TrueClass or Symbol
1262
- # The + "" forces an 'unfreeze' if necessary
1263
- vs.force_encoding(Stomp::UTF8) if vs.respond_to?(:force_encoding)
1264
- raise Stomp::Error::UTF8ValidationError unless valid_utf8?(vs)
1265
- end
1266
- end
1267
- end
1268
-
1269
- #
1270
- def _encodeHeaders(h)
1271
- eh = {}
1272
- h.each_pair do |k,v|
1273
- # Keys are symbolized
1274
- ks = k.to_s
1275
- if v.is_a?(Array)
1276
- kenc = Stomp::HeaderCodec::encode(ks)
1277
- eh[kenc] = []
1278
- v.each do |e|
1279
- eh[kenc] << Stomp::HeaderCodec::encode(e)
1280
- end
1281
- else
1282
- vs = v.to_s
1283
- eh[Stomp::HeaderCodec::encode(ks)] = Stomp::HeaderCodec::encode(vs)
1284
- end
1285
- end
1286
- eh
1287
- end
1288
-
1289
- #
1290
- def _decodeHeaders(h)
1291
- dh = {}
1292
- h.each_pair do |k,v|
1293
- # Keys here are NOT! symbolized
1294
- if v.is_a?(Array)
1295
- kdec = Stomp::HeaderCodec::decode(k)
1296
- dh[kdec] = []
1297
- v.each do |e|
1298
- dh[kdec] << Stomp::HeaderCodec::decode(e)
1299
- end
1300
- else
1301
- vs = v.to_s
1302
- dh[Stomp::HeaderCodec::decode(k)] = Stomp::HeaderCodec::decode(vs)
1303
- end
1304
- end
1305
- dh
1306
- end
1307
-
1308
552
  end # class
1309
553
 
1310
554
  end # module