mbus 1.0.0
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.
- checksums.yaml +7 -0
- data/README.mediawiki +169 -0
- data/Rakefile +24 -0
- data/bin/console +11 -0
- data/bin/messagebus_swarm +77 -0
- data/lib/messagebus.rb +62 -0
- data/lib/messagebus/client.rb +166 -0
- data/lib/messagebus/cluster_map.rb +161 -0
- data/lib/messagebus/connection.rb +118 -0
- data/lib/messagebus/consumer.rb +447 -0
- data/lib/messagebus/custom_errors.rb +37 -0
- data/lib/messagebus/dottable_hash.rb +113 -0
- data/lib/messagebus/error_status.rb +42 -0
- data/lib/messagebus/logger.rb +45 -0
- data/lib/messagebus/message.rb +168 -0
- data/lib/messagebus/messagebus_types.rb +107 -0
- data/lib/messagebus/producer.rb +187 -0
- data/lib/messagebus/swarm.rb +49 -0
- data/lib/messagebus/swarm/controller.rb +296 -0
- data/lib/messagebus/swarm/drone.rb +195 -0
- data/lib/messagebus/swarm/drone/logging_worker.rb +53 -0
- data/lib/messagebus/validations.rb +68 -0
- data/lib/messagebus/version.rb +36 -0
- data/messagebus.gemspec +29 -0
- data/spec/messagebus/client_spec.rb +157 -0
- data/spec/messagebus/cluster_map_spec.rb +178 -0
- data/spec/messagebus/consumer_spec.rb +338 -0
- data/spec/messagebus/dottable_hash_spec.rb +137 -0
- data/spec/messagebus/message_spec.rb +93 -0
- data/spec/messagebus/producer_spec.rb +147 -0
- data/spec/messagebus/swarm/controller_spec.rb +73 -0
- data/spec/messagebus/validations_spec.rb +71 -0
- data/spec/spec_helper.rb +10 -0
- data/vendor/gems/stomp.rb +23 -0
- data/vendor/gems/stomp/client.rb +360 -0
- data/vendor/gems/stomp/connection.rb +583 -0
- data/vendor/gems/stomp/errors.rb +39 -0
- data/vendor/gems/stomp/ext/hash.rb +24 -0
- data/vendor/gems/stomp/message.rb +68 -0
- metadata +138 -0
@@ -0,0 +1,583 @@
|
|
1
|
+
require 'socket'
|
2
|
+
require 'timeout'
|
3
|
+
require 'io/wait'
|
4
|
+
require 'stomp/errors'
|
5
|
+
require 'stomp/message'
|
6
|
+
require 'stomp/ext/hash'
|
7
|
+
|
8
|
+
module Stomp
|
9
|
+
|
10
|
+
# Low level connection which maps commands and supports
|
11
|
+
# synchronous receives
|
12
|
+
class Connection
|
13
|
+
attr_reader :connection_frame
|
14
|
+
attr_reader :disconnect_receipt
|
15
|
+
#alias :obj_send :send
|
16
|
+
|
17
|
+
def self.default_port(ssl)
|
18
|
+
ssl ? 61612 : 61613
|
19
|
+
end
|
20
|
+
|
21
|
+
# A new Connection object accepts the following parameters:
|
22
|
+
#
|
23
|
+
# login (String, default : '')
|
24
|
+
# passcode (String, default : '')
|
25
|
+
# host (String, default : 'localhost')
|
26
|
+
# port (Integer, default : 61613)
|
27
|
+
# reliable (Boolean, default : false)
|
28
|
+
# reconnect_delay (Integer, default : 5)
|
29
|
+
#
|
30
|
+
# e.g. c = Connection.new("username", "password", "localhost", 61613, true)
|
31
|
+
#
|
32
|
+
# Hash:
|
33
|
+
#
|
34
|
+
# hash = {
|
35
|
+
# :hosts => [
|
36
|
+
# {:login => "login1", :passcode => "passcode1", :host => "localhost", :port => 61616, :ssl => false},
|
37
|
+
# {:login => "login2", :passcode => "passcode2", :host => "remotehost", :port => 61617, :ssl => false}
|
38
|
+
# ],
|
39
|
+
# :initial_reconnect_delay => 0.01,
|
40
|
+
# :max_reconnect_delay => 30.0,
|
41
|
+
# :use_exponential_back_off => true,
|
42
|
+
# :back_off_multiplier => 2,
|
43
|
+
# :max_reconnect_attempts => 0,
|
44
|
+
# :randomize => false,
|
45
|
+
# :backup => false,
|
46
|
+
# :timeout => -1,
|
47
|
+
# :connect_headers => {},
|
48
|
+
# :parse_timeout => 5,
|
49
|
+
# :logger => nil,
|
50
|
+
# }
|
51
|
+
#
|
52
|
+
# e.g. c = Connection.new(hash)
|
53
|
+
#
|
54
|
+
# TODO
|
55
|
+
# Stomp URL :
|
56
|
+
# A Stomp URL must begin with 'stomp://' and can be in one of the following forms:
|
57
|
+
#
|
58
|
+
# stomp://host:port
|
59
|
+
# stomp://host.domain.tld:port
|
60
|
+
# stomp://user:pass@host:port
|
61
|
+
# stomp://user:pass@host.domain.tld:port
|
62
|
+
#
|
63
|
+
def initialize(login = '', passcode = '', host = 'localhost', port = 61613, reliable = false, reconnect_delay = 5, connect_headers = {})
|
64
|
+
@received_messages = []
|
65
|
+
|
66
|
+
if login.is_a?(Hash)
|
67
|
+
hashed_initialize(login)
|
68
|
+
else
|
69
|
+
@host = host
|
70
|
+
@port = port
|
71
|
+
@login = login
|
72
|
+
@passcode = passcode
|
73
|
+
@reliable = reliable
|
74
|
+
@reconnect_delay = reconnect_delay
|
75
|
+
@connect_headers = connect_headers
|
76
|
+
@ssl = false
|
77
|
+
@parse_timeout = 5 # To override, use hashed parameters
|
78
|
+
@logger = nil # To override, use hashed parameters
|
79
|
+
end
|
80
|
+
|
81
|
+
# Use Mutexes: only one lock per each thread
|
82
|
+
# Revert to original implementation attempt
|
83
|
+
@transmit_semaphore = Mutex.new
|
84
|
+
@read_semaphore = Mutex.new
|
85
|
+
@socket_semaphore = Mutex.new
|
86
|
+
@subscriptions = {}
|
87
|
+
@failure = nil
|
88
|
+
@connection_attempts = 0
|
89
|
+
|
90
|
+
socket
|
91
|
+
end
|
92
|
+
|
93
|
+
def hashed_initialize(params)
|
94
|
+
@parameters = refine_params(params)
|
95
|
+
@reliable = true
|
96
|
+
@reconnect_delay = @parameters[:initial_reconnect_delay]
|
97
|
+
@connect_headers = @parameters[:connect_headers]
|
98
|
+
@parse_timeout = @parameters[:parse_timeout]
|
99
|
+
@logger = Logger.new( @parameters[:logger].instance_variable_get( "@logdev" ).filename )
|
100
|
+
@logger.formatter = @parameters[:logger].formatter
|
101
|
+
#sets the first host to connect
|
102
|
+
change_host
|
103
|
+
if @logger && @logger.respond_to?(:on_connecting)
|
104
|
+
@logger.on_connecting(log_params)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def socket
|
109
|
+
|
110
|
+
@socket_semaphore.synchronize do
|
111
|
+
used_socket = @socket
|
112
|
+
used_socket = nil if closed?
|
113
|
+
while used_socket.nil? || !@failure.nil?
|
114
|
+
@failure = nil
|
115
|
+
begin
|
116
|
+
|
117
|
+
@logger.info("Opening socket : #{log_params}")
|
118
|
+
used_socket = open_socket
|
119
|
+
# Open complete
|
120
|
+
|
121
|
+
@logger.info("Opened socket : #{log_params}")
|
122
|
+
connect(used_socket)
|
123
|
+
|
124
|
+
@connection_attempts = 0
|
125
|
+
rescue
|
126
|
+
@failure = $!
|
127
|
+
used_socket = nil
|
128
|
+
raise unless @reliable
|
129
|
+
|
130
|
+
@logger.error "connect to #{@host} failed: #{$!} will retry(##{@connection_attempts}) in #{@reconnect_delay}\n"
|
131
|
+
|
132
|
+
if max_reconnect_attempts?
|
133
|
+
@reconnect_delay = 0.01
|
134
|
+
@connection_attempts = 0
|
135
|
+
raise Stomp::Error::MaxReconnectAttempts
|
136
|
+
end
|
137
|
+
|
138
|
+
sleep(@reconnect_delay)
|
139
|
+
|
140
|
+
@connection_attempts += 1
|
141
|
+
|
142
|
+
if @parameters
|
143
|
+
change_host
|
144
|
+
increase_reconnect_delay
|
145
|
+
end
|
146
|
+
|
147
|
+
unless @failure
|
148
|
+
@reconnect_delay = 0
|
149
|
+
end
|
150
|
+
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
@socket = used_socket
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
def refine_params(params)
|
159
|
+
params = params.uncamelize_and_symbolize_keys
|
160
|
+
|
161
|
+
default_params = {
|
162
|
+
:connect_headers => {},
|
163
|
+
# Failover parameters
|
164
|
+
:initial_reconnect_delay => 0.01,
|
165
|
+
:max_reconnect_delay => 30.0,
|
166
|
+
:use_exponential_back_off => true,
|
167
|
+
:back_off_multiplier => 2,
|
168
|
+
:max_reconnect_attempts => 2,
|
169
|
+
:randomize => false,
|
170
|
+
:backup => false,
|
171
|
+
:timeout => -1,
|
172
|
+
# Parse Timeout
|
173
|
+
:parse_timeout => 5
|
174
|
+
}
|
175
|
+
|
176
|
+
default_params.merge(params)
|
177
|
+
|
178
|
+
end
|
179
|
+
|
180
|
+
def change_host
|
181
|
+
@parameters[:hosts] = @parameters[:hosts].sort_by { rand } if @parameters[:randomize]
|
182
|
+
|
183
|
+
# Set first as master and send it to the end of array
|
184
|
+
current_host = @parameters[:hosts].shift
|
185
|
+
@parameters[:hosts] << current_host
|
186
|
+
|
187
|
+
@ssl = current_host[:ssl]
|
188
|
+
@host = current_host[:host]
|
189
|
+
@port = current_host[:port] || Connection::default_port(@ssl)
|
190
|
+
@login = current_host[:login] || ""
|
191
|
+
@passcode = current_host[:passcode] || ""
|
192
|
+
|
193
|
+
end
|
194
|
+
|
195
|
+
def max_reconnect_attempts?
|
196
|
+
!(@parameters.nil? || @parameters[:max_reconnect_attempts].nil?) && @parameters[:max_reconnect_attempts] != 0 && @connection_attempts >= @parameters[:max_reconnect_attempts]
|
197
|
+
end
|
198
|
+
|
199
|
+
def increase_reconnect_delay
|
200
|
+
|
201
|
+
@reconnect_delay *= @parameters[:back_off_multiplier] if @parameters[:use_exponential_back_off]
|
202
|
+
@reconnect_delay = @parameters[:max_reconnect_delay] if @reconnect_delay > @parameters[:max_reconnect_delay]
|
203
|
+
@reconnect_delay
|
204
|
+
end
|
205
|
+
|
206
|
+
# Is this connection open?
|
207
|
+
def open?
|
208
|
+
!@closed
|
209
|
+
end
|
210
|
+
|
211
|
+
# Is this connection closed?
|
212
|
+
def closed?
|
213
|
+
@closed
|
214
|
+
end
|
215
|
+
|
216
|
+
# Begin a transaction, requires a name for the transaction
|
217
|
+
def begin(name, headers = {})
|
218
|
+
headers[:transaction] = name
|
219
|
+
transmit("BEGIN", headers)
|
220
|
+
end
|
221
|
+
|
222
|
+
# Acknowledge a message, used when a subscription has specified
|
223
|
+
# client acknowledgement ( connection.subscribe "/queue/a", :ack => 'client'g
|
224
|
+
#
|
225
|
+
# Accepts a transaction header ( :transaction => 'some_transaction_id' )
|
226
|
+
def ack(message_id, headers = {})
|
227
|
+
headers['message-id'] = message_id
|
228
|
+
transmit("ACK", headers)
|
229
|
+
end
|
230
|
+
|
231
|
+
# Nacknowledge a message, used when a subscription has specified
|
232
|
+
# client acknowledgement ( connection.subscribe "/queue/a", :ack => 'client'g
|
233
|
+
#
|
234
|
+
# Accepts a transaction header ( :transaction => 'some_transaction_id' )
|
235
|
+
def nack(message_id, headers = {})
|
236
|
+
headers['message-id'] = message_id
|
237
|
+
transmit("NACK", headers)
|
238
|
+
end
|
239
|
+
|
240
|
+
# Sends a keepalive frame to the server so that stale connection won't be reclaimed.
|
241
|
+
#
|
242
|
+
# Accepts a transaction header ( :transaction => 'some_transaction_id' )
|
243
|
+
def keepalive(headers = {})
|
244
|
+
transmit("KEEPALIVE", headers)
|
245
|
+
end
|
246
|
+
|
247
|
+
# Credit a message, used when a subscription has specified
|
248
|
+
def credit(message_id, headers = {})
|
249
|
+
headers['message-id'] = message_id
|
250
|
+
transmit("CREDIT", headers)
|
251
|
+
end
|
252
|
+
|
253
|
+
# Commit a transaction by name
|
254
|
+
def commit(name, headers = {})
|
255
|
+
headers[:transaction] = name
|
256
|
+
transmit("COMMIT", headers)
|
257
|
+
end
|
258
|
+
|
259
|
+
# Abort a transaction by name
|
260
|
+
def abort(name, headers = {})
|
261
|
+
headers[:transaction] = name
|
262
|
+
transmit("ABORT", headers)
|
263
|
+
end
|
264
|
+
|
265
|
+
# Subscribe to a destination, must specify a name
|
266
|
+
def subscribe(name, headers = {}, subId = nil)
|
267
|
+
headers[:destination] = name
|
268
|
+
transmit("SUBSCRIBE", headers)
|
269
|
+
|
270
|
+
# Store the sub so that we can replay if we reconnect.
|
271
|
+
if @reliable
|
272
|
+
subId = name if subId.nil?
|
273
|
+
@subscriptions[subId] = headers
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
# Unsubscribe from a destination, must specify a name
|
278
|
+
def unsubscribe(name, headers = {}, subId = nil)
|
279
|
+
headers[:destination] = name
|
280
|
+
transmit("UNSUBSCRIBE", headers)
|
281
|
+
if @reliable
|
282
|
+
subId = name if subId.nil?
|
283
|
+
@subscriptions.delete(subId)
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
287
|
+
# Publish message to destination
|
288
|
+
#
|
289
|
+
# To disable content length header ( :suppress_content_length => true )
|
290
|
+
# Accepts a transaction header ( :transaction => 'some_transaction_id' )
|
291
|
+
def publish(destination, message, headers = {})
|
292
|
+
headers[:destination] = destination
|
293
|
+
transmit("SEND", headers, message)
|
294
|
+
end
|
295
|
+
|
296
|
+
def obj_send(*args)
|
297
|
+
__send__(*args)
|
298
|
+
end
|
299
|
+
|
300
|
+
# Send a message back to the source or to the dead letter queue
|
301
|
+
#
|
302
|
+
# Accepts a dead letter queue option ( :dead_letter_queue => "/queue/DLQ" )
|
303
|
+
# Accepts a limit number of redeliveries option ( :max_redeliveries => 6 )
|
304
|
+
# Accepts a force client acknowledgement option (:force_client_ack => true)
|
305
|
+
def unreceive(message, options = {})
|
306
|
+
options = { :dead_letter_queue => "/queue/DLQ", :max_redeliveries => 6 }.merge options
|
307
|
+
# Lets make sure all keys are symbols
|
308
|
+
message.headers = message.headers.symbolize_keys
|
309
|
+
|
310
|
+
retry_count = message.headers[:retry_count].to_i || 0
|
311
|
+
message.headers[:retry_count] = retry_count + 1
|
312
|
+
transaction_id = "transaction-#{message.headers[:'message-id']}-#{retry_count}"
|
313
|
+
message_id = message.headers.delete(:'message-id')
|
314
|
+
|
315
|
+
begin
|
316
|
+
self.begin transaction_id
|
317
|
+
|
318
|
+
if client_ack?(message) || options[:force_client_ack]
|
319
|
+
self.ack(message_id, :transaction => transaction_id)
|
320
|
+
end
|
321
|
+
|
322
|
+
if retry_count <= options[:max_redeliveries]
|
323
|
+
self.publish(message.headers[:destination], message.body, message.headers.merge(:transaction => transaction_id))
|
324
|
+
else
|
325
|
+
# Poison ack, sending the message to the DLQ
|
326
|
+
self.publish(options[:dead_letter_queue], message.body, message.headers.merge(:transaction => transaction_id, :original_destination => message.headers[:destination], :persistent => true))
|
327
|
+
end
|
328
|
+
self.commit transaction_id
|
329
|
+
rescue Exception => exception
|
330
|
+
self.abort transaction_id
|
331
|
+
raise exception
|
332
|
+
end
|
333
|
+
end
|
334
|
+
|
335
|
+
def client_ack?(message)
|
336
|
+
headers = @subscriptions[message.headers[:destination]]
|
337
|
+
!headers.nil? && headers[:ack] == "client"
|
338
|
+
end
|
339
|
+
|
340
|
+
# Close this connection
|
341
|
+
def disconnect(headers = {})
|
342
|
+
transmit("DISCONNECT", headers)
|
343
|
+
headers = headers.symbolize_keys
|
344
|
+
@disconnect_receipt = receive if headers[:receipt]
|
345
|
+
@logger.info("Connection disconnected for : #{log_params}")
|
346
|
+
close_socket
|
347
|
+
end
|
348
|
+
|
349
|
+
# Return a pending message if one is available, otherwise
|
350
|
+
# return nil
|
351
|
+
def poll
|
352
|
+
# No need for a read lock here. The receive method eventually fulfills
|
353
|
+
# that requirement.
|
354
|
+
return nil if @socket.nil? || !@socket.ready?
|
355
|
+
receive
|
356
|
+
end
|
357
|
+
|
358
|
+
# Receive a frame, repeatedly retries until the frame is received
|
359
|
+
def __old_receive
|
360
|
+
# The receive may fail so we may need to retry.
|
361
|
+
while TRUE
|
362
|
+
begin
|
363
|
+
|
364
|
+
used_socket = socket
|
365
|
+
return _receive(used_socket)
|
366
|
+
rescue => e
|
367
|
+
@failure = $!
|
368
|
+
raise unless @reliable
|
369
|
+
@logger.error "receive failed: #{e}\n #{e.backtrace.join("\n")}"
|
370
|
+
nil
|
371
|
+
end
|
372
|
+
end
|
373
|
+
end
|
374
|
+
|
375
|
+
def receive
|
376
|
+
super_result = __old_receive
|
377
|
+
if super_result.nil? && @reliable && !closed?
|
378
|
+
errstr = "connection.receive returning EOF as nil - clearing socket, resetting connection.\n"
|
379
|
+
@logger.info(errstr)
|
380
|
+
@socket = nil
|
381
|
+
super_result = __old_receive
|
382
|
+
end
|
383
|
+
return super_result
|
384
|
+
end
|
385
|
+
|
386
|
+
private
|
387
|
+
|
388
|
+
def _receive( read_socket )
|
389
|
+
|
390
|
+
while TRUE
|
391
|
+
begin
|
392
|
+
@read_semaphore.synchronize do
|
393
|
+
# Throw away leading newlines, which are actually trailing
|
394
|
+
# newlines from the preceding message.
|
395
|
+
Timeout::timeout(@parse_timeout, Stomp::Error::PacketReceiptTimeout) do
|
396
|
+
begin
|
397
|
+
last_char = read_socket.getc
|
398
|
+
return nil if last_char.nil?
|
399
|
+
end until parse_char(last_char) != "\n"
|
400
|
+
read_socket.ungetc(last_char)
|
401
|
+
end
|
402
|
+
|
403
|
+
# If the reading hangs for more than X seconds, abort the parsing process.
|
404
|
+
# X defaults to 5. Override allowed in connection hash parameters.
|
405
|
+
Timeout::timeout(@parse_timeout, Stomp::Error::PacketParsingTimeout) do
|
406
|
+
|
407
|
+
# Reads the beginning of the message until it runs into a empty line
|
408
|
+
message_header = ''
|
409
|
+
line = ''
|
410
|
+
begin
|
411
|
+
message_header << line
|
412
|
+
line = read_socket.gets
|
413
|
+
return nil if line.nil?
|
414
|
+
end until line =~ /^\s?\n$/
|
415
|
+
|
416
|
+
# Checks if it includes content_length header
|
417
|
+
content_length = message_header.match /content-length\s?:\s?(\d+)\s?\n/
|
418
|
+
message_body = ''
|
419
|
+
|
420
|
+
# If it does, reads the specified amount of bytes
|
421
|
+
char = ''
|
422
|
+
if content_length
|
423
|
+
message_body = read_socket.read content_length[1].to_i
|
424
|
+
raise Stomp::Error::InvalidMessageLength unless parse_char(read_socket.getc) == "\0"
|
425
|
+
# Else reads, the rest of the message until the first \0
|
426
|
+
else
|
427
|
+
message_body << char while (char = parse_char(read_socket.getc)) != "\0"
|
428
|
+
end
|
429
|
+
|
430
|
+
# Adds the excluded \n and \0 and tries to create a new message with it
|
431
|
+
return Message.new(message_header + "\n" + message_body + "\0")
|
432
|
+
end
|
433
|
+
end
|
434
|
+
rescue Stomp::Error::PacketReceiptTimeout =>pe
|
435
|
+
@logger.debug("Packet not received. Continuing")
|
436
|
+
sleep 0.01
|
437
|
+
next
|
438
|
+
rescue Exception => e
|
439
|
+
@logger.info(e.inspect)
|
440
|
+
end
|
441
|
+
break
|
442
|
+
end
|
443
|
+
end
|
444
|
+
|
445
|
+
def parse_char(char)
|
446
|
+
RUBY_VERSION > '1.9' ? char : char.chr
|
447
|
+
end
|
448
|
+
|
449
|
+
def transmit(command, headers = {}, body = '')
|
450
|
+
|
451
|
+
# The transmit may fail so we may need to retry.
|
452
|
+
# But we should have some decent limit on retries
|
453
|
+
max_transmit_attempts = 5
|
454
|
+
transmit_attempts = 0
|
455
|
+
|
456
|
+
while TRUE
|
457
|
+
begin
|
458
|
+
used_socket = socket
|
459
|
+
_transmit(used_socket, command, headers, body)
|
460
|
+
return
|
461
|
+
rescue Stomp::Error::MaxReconnectAttempts => e
|
462
|
+
errstr = "transmit to #{@host} failed after max connection retry attempts: #{$!}\n Stack Trace: #{caller}"
|
463
|
+
@logger.error(errstr)
|
464
|
+
@failure = $!
|
465
|
+
raise
|
466
|
+
rescue => e
|
467
|
+
@failure = $!
|
468
|
+
if transmit_attempts >= max_transmit_attempts
|
469
|
+
errstr = "transmit to #{@host} failed after max transmit retry attempts: #{$!}\n Stack Trace: #{caller}"
|
470
|
+
@logger.error(errstr)
|
471
|
+
raise Stomp::Error::MaxReconnectAttempts
|
472
|
+
end
|
473
|
+
transmit_attempts = transmit_attempts + 1
|
474
|
+
end
|
475
|
+
end
|
476
|
+
end
|
477
|
+
|
478
|
+
def _transmit(used_socket, command, headers = {}, body = '')
|
479
|
+
@transmit_semaphore.synchronize do
|
480
|
+
# Handle nil body
|
481
|
+
body = '' if body.nil?
|
482
|
+
# The content-length should be expressed in bytes.
|
483
|
+
# Ruby 1.8: String#length => # of bytes; Ruby 1.9: String#length => # of characters
|
484
|
+
# With Unicode strings, # of bytes != # of characters. So, use String#bytesize when available.
|
485
|
+
body_length_bytes = body.respond_to?(:bytesize) ? body.bytesize : body.length
|
486
|
+
|
487
|
+
# ActiveMQ interprets every message as a BinaryMessage
|
488
|
+
# if content_length header is included.
|
489
|
+
# Using :suppress_content_length => true will suppress this behaviour
|
490
|
+
# and ActiveMQ will interpret the message as a TextMessage.
|
491
|
+
# For more information refer to http://juretta.com/log/2009/05/24/activemq-jms-stomp/
|
492
|
+
# Lets send this header in the message, so it can maintain state when using unreceive
|
493
|
+
headers['content-length'] = "#{body_length_bytes}" unless headers[:suppress_content_length]
|
494
|
+
used_socket.puts command
|
495
|
+
headers.each {|k,v| used_socket.puts "#{k}:#{v}" }
|
496
|
+
used_socket.puts "content-type: text/plain; charset=UTF-8"
|
497
|
+
used_socket.puts
|
498
|
+
used_socket.write body
|
499
|
+
used_socket.write "\0"
|
500
|
+
end
|
501
|
+
end
|
502
|
+
|
503
|
+
def open_tcp_socket
|
504
|
+
tcp_socket = TCPSocket.open @host, @port
|
505
|
+
|
506
|
+
tcp_socket
|
507
|
+
end
|
508
|
+
|
509
|
+
def open_ssl_socket
|
510
|
+
require 'openssl' unless defined?(OpenSSL)
|
511
|
+
ctx = OpenSSL::SSL::SSLContext.new
|
512
|
+
|
513
|
+
# For client certificate authentication:
|
514
|
+
# key_path = ENV["STOMP_KEY_PATH"] || "~/stomp_keys"
|
515
|
+
# ctx.cert = OpenSSL::X509::Certificate.new("#{key_path}/client.cer")
|
516
|
+
# ctx.key = OpenSSL::PKey::RSA.new("#{key_path}/client.keystore")
|
517
|
+
|
518
|
+
# For server certificate authentication:
|
519
|
+
# truststores = OpenSSL::X509::Store.new
|
520
|
+
# truststores.add_file("#{key_path}/client.ts")
|
521
|
+
# ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
522
|
+
# ctx.cert_store = truststores
|
523
|
+
|
524
|
+
ctx.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
525
|
+
|
526
|
+
ssl = OpenSSL::SSL::SSLSocket.new(open_tcp_socket, ctx)
|
527
|
+
def ssl.ready?
|
528
|
+
! @rbuffer.empty? || @io.ready?
|
529
|
+
end
|
530
|
+
ssl.connect
|
531
|
+
ssl
|
532
|
+
end
|
533
|
+
|
534
|
+
def close_socket
|
535
|
+
begin
|
536
|
+
# Need to set @closed = true before closing the socket
|
537
|
+
# within the @read_semaphore thread
|
538
|
+
@read_semaphore.synchronize do
|
539
|
+
@closed = true
|
540
|
+
@socket.close
|
541
|
+
end
|
542
|
+
rescue
|
543
|
+
#Ignoring if already closed
|
544
|
+
end
|
545
|
+
@closed
|
546
|
+
end
|
547
|
+
|
548
|
+
def open_socket
|
549
|
+
used_socket = @ssl ? open_ssl_socket : open_tcp_socket
|
550
|
+
# try to close the old connection if any
|
551
|
+
close_socket
|
552
|
+
|
553
|
+
@closed = false
|
554
|
+
# Use keepalive
|
555
|
+
used_socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, true)
|
556
|
+
used_socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
|
557
|
+
used_socket
|
558
|
+
end
|
559
|
+
|
560
|
+
def connect(used_socket)
|
561
|
+
headers = @connect_headers.clone
|
562
|
+
headers[:login] = @login
|
563
|
+
headers[:passcode] = @passcode
|
564
|
+
_transmit(used_socket, "CONNECT", headers)
|
565
|
+
|
566
|
+
@connection_frame = _receive(used_socket)
|
567
|
+
@disconnect_receipt = nil
|
568
|
+
# replay any subscriptions.
|
569
|
+
@subscriptions.each { |k,v| _transmit(used_socket, "SUBSCRIBE", v) }
|
570
|
+
end
|
571
|
+
|
572
|
+
def log_params
|
573
|
+
#lparms = @parameters.clone
|
574
|
+
lparms = Hash.new
|
575
|
+
lparms[:cur_host] = @host
|
576
|
+
lparms[:cur_port] = @port
|
577
|
+
lparms[:cur_recondelay] = @reconnect_delay
|
578
|
+
lparms[:cur_conattempts] = @connection_attempts
|
579
|
+
lparms
|
580
|
+
end
|
581
|
+
end
|
582
|
+
|
583
|
+
end
|