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.
- data/CHANGELOG.rdoc +11 -0
- data/README.rdoc +38 -26
- data/Rakefile +3 -0
- data/bin/catstomp +34 -34
- data/bin/stompcat +36 -36
- data/examples/client11_ex1.rb +64 -55
- data/examples/client11_putget1.rb +47 -35
- data/examples/conn11_ex1.rb +59 -51
- data/examples/conn11_ex2.rb +59 -50
- data/examples/conn11_hb1.rb +35 -26
- data/examples/consumer.rb +25 -12
- data/examples/get11conn_ex1.rb +97 -89
- data/examples/get11conn_ex2.rb +55 -47
- data/examples/logexamp.rb +66 -52
- data/examples/logexamp_ssl.rb +66 -52
- data/examples/publisher.rb +21 -10
- data/examples/put11conn_ex1.rb +35 -24
- data/examples/putget11_rh1.rb +66 -56
- data/examples/slogger.rb +65 -52
- data/examples/ssl_uc1.rb +24 -13
- data/examples/ssl_uc1_ciphers.rb +28 -15
- data/examples/ssl_uc2.rb +26 -16
- data/examples/ssl_uc2_ciphers.rb +31 -18
- data/examples/ssl_uc3.rb +25 -14
- data/examples/ssl_uc3_ciphers.rb +31 -18
- data/examples/ssl_uc4.rb +26 -15
- data/examples/ssl_uc4_ciphers.rb +32 -19
- data/examples/ssl_ucx_default_ciphers.rb +25 -12
- data/examples/stomp11_common.rb +16 -15
- data/examples/topic_consumer.rb +23 -10
- data/examples/topic_publisher.rb +22 -8
- data/lib/client/utils.rb +116 -0
- data/lib/connection/heartbeats.rb +173 -0
- data/lib/connection/netio.rb +322 -0
- data/lib/connection/utf8.rb +294 -0
- data/lib/connection/utils.rb +104 -0
- data/lib/stomp/client.rb +127 -179
- data/lib/stomp/codec.rb +5 -2
- data/lib/stomp/connection.rb +109 -865
- data/lib/stomp/constants.rb +52 -33
- data/lib/stomp/errors.rb +56 -5
- data/lib/stomp/ext/hash.rb +4 -0
- data/lib/stomp/message.rb +49 -29
- data/lib/stomp/sslparams.rb +83 -71
- data/lib/stomp/version.rb +3 -1
- data/lib/stomp.rb +18 -9
- data/stomp.gemspec +58 -3
- data/test/test_client.rb +28 -1
- data/test/test_codec.rb +8 -2
- data/test/test_connection.rb +29 -0
- data/test/test_connection1p.rb +31 -16
- data/test/test_helper.rb +20 -3
- data/test/test_message.rb +8 -3
- data/test/test_ssl.rb +10 -4
- data/test/tlogger.rb +16 -15
- metadata +59 -4
data/lib/stomp/connection.rb
CHANGED
@@ -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
|
43
|
+
# A new Connection object can be initialized using two forms:
|
27
44
|
#
|
28
|
-
#
|
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
|
-
#
|
61
|
-
#
|
62
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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
|
-
|
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
|
-
#
|
252
|
+
# open? tests if this connection is open.
|
228
253
|
def open?
|
229
254
|
!@closed
|
230
255
|
end
|
231
256
|
|
232
|
-
#
|
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
|
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
|
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
|
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
|
-
#
|
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
|
354
|
-
# Accepts a
|
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
|
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,
|
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,
|
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
|
-
#
|
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
|
-
#
|
410
|
-
#
|
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
|
-
#
|
420
|
-
|
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
|
-
|
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
|
-
#
|
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
|
-
#
|
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
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
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
|
-
#
|
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
|
-
#
|
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
|
-
|
487
|
-
|
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
|
-
|
490
|
-
|
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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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
|