stompserver_ng 1.0.6
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/History.txt +159 -0
- data/Manifest.txt +71 -0
- data/README.txt +172 -0
- data/Rakefile +38 -0
- data/STATUS +5 -0
- data/bin/stompserver_ng +63 -0
- data/client/README.txt +1 -0
- data/client/both.rb +25 -0
- data/client/consume.rb +14 -0
- data/client/send.rb +17 -0
- data/config/stompserver_ng.conf +11 -0
- data/etc/19xcompat/notes.txt +223 -0
- data/etc/arutils/README-activerecord.txt +78 -0
- data/etc/arutils/cre_mysql.rb +34 -0
- data/etc/arutils/cre_postgres.rb +33 -0
- data/etc/arutils/cre_sqlite3.rb +28 -0
- data/etc/arutils/mysql_boot.sql +12 -0
- data/etc/arutils/postgres_boot.sql +14 -0
- data/etc/database.mysql.yml +9 -0
- data/etc/database.postgres.yml +9 -0
- data/etc/passwd.example +3 -0
- data/etc/ppqinfo.rb +15 -0
- data/etc/runserver.sh +17 -0
- data/etc/stompserver_ng +50 -0
- data/etc/stompserver_ng.conf +13 -0
- data/lib/stomp_server_ng.rb +471 -0
- data/lib/stomp_server_ng/protocols/http.rb +128 -0
- data/lib/stomp_server_ng/protocols/stomp.rb +407 -0
- data/lib/stomp_server_ng/qmonitor.rb +58 -0
- data/lib/stomp_server_ng/queue.rb +248 -0
- data/lib/stomp_server_ng/queue/activerecord_queue.rb +118 -0
- data/lib/stomp_server_ng/queue/ar_message.rb +21 -0
- data/lib/stomp_server_ng/queue/ar_reconnect.rb +18 -0
- data/lib/stomp_server_ng/queue/dbm_queue.rb +72 -0
- data/lib/stomp_server_ng/queue/file_queue.rb +56 -0
- data/lib/stomp_server_ng/queue/memory_queue.rb +64 -0
- data/lib/stomp_server_ng/queue_manager.rb +302 -0
- data/lib/stomp_server_ng/stomp_auth.rb +26 -0
- data/lib/stomp_server_ng/stomp_frame.rb +32 -0
- data/lib/stomp_server_ng/stomp_frame_recognizer.rb +77 -0
- data/lib/stomp_server_ng/stomp_id.rb +32 -0
- data/lib/stomp_server_ng/stomp_user.rb +17 -0
- data/lib/stomp_server_ng/test_server.rb +21 -0
- data/lib/stomp_server_ng/topic_manager.rb +46 -0
- data/setup.rb +1585 -0
- data/stompserver_ng.gemspec +136 -0
- data/test/devserver/props.yaml +5 -0
- data/test/devserver/runserver.sh +16 -0
- data/test/devserver/stompserver_ng.dbm.conf +12 -0
- data/test/devserver/stompserver_ng.file.conf +12 -0
- data/test/devserver/stompserver_ng.memory.conf +12 -0
- data/test/noserver/mocklogger.rb +12 -0
- data/test/noserver/test_queue_manager.rb +134 -0
- data/test/noserver/test_stomp_frame.rb +138 -0
- data/test/noserver/test_topic_manager.rb +79 -0
- data/test/noserver/ts_all_no_server.rb +12 -0
- data/test/props.yaml +5 -0
- data/test/runalltests.sh +14 -0
- data/test/runtest.sh +4 -0
- data/test/test_0000_base.rb +107 -0
- data/test/test_0001_conn.rb +47 -0
- data/test/test_0002_conn_sr.rb +94 -0
- data/test/test_0006_client.rb +41 -0
- data/test/test_0011_send_recv.rb +74 -0
- data/test/test_0015_ack_conn.rb +78 -0
- data/test/test_0017_ack_client.rb +78 -0
- data/test/test_0019_ack_no_ack.rb +145 -0
- data/test/test_0022_ack_noack_conn.rb +123 -0
- data/test/test_0030_subscr_id.rb +44 -0
- data/test/test_0040_receipt_conn.rb +87 -0
- data/test/ts_all_server.rb +10 -0
- metadata +196 -0
@@ -0,0 +1,72 @@
|
|
1
|
+
|
2
|
+
module StompServer
|
3
|
+
class DBMQueue < Queue
|
4
|
+
|
5
|
+
def initialize *args
|
6
|
+
super
|
7
|
+
|
8
|
+
@@log = Logger.new(STDOUT)
|
9
|
+
@@log.level = StompServer::LogHelper.get_loglevel()
|
10
|
+
|
11
|
+
# Please don't use dbm files for storing large frames, it's problematic at best and uses large amounts of memory.
|
12
|
+
# sdbm croaks on marshalled data that contains certain characters, so we don't use it at all
|
13
|
+
@dbm = false
|
14
|
+
if RUBY_PLATFORM =~/linux|bsd/
|
15
|
+
types = ['bdb','dbm','gdbm']
|
16
|
+
else
|
17
|
+
types = ['bdb','gdbm']
|
18
|
+
end
|
19
|
+
types.each do |dbtype|
|
20
|
+
begin
|
21
|
+
require dbtype
|
22
|
+
@dbm = dbtype
|
23
|
+
@@log.info "#{@dbm} loaded"
|
24
|
+
break
|
25
|
+
rescue LoadError => e
|
26
|
+
end
|
27
|
+
end
|
28
|
+
raise "No DBM library found. Tried bdb,dbm,gdbm" unless @dbm
|
29
|
+
@db = Hash.new
|
30
|
+
@queues.keys.each {|q| _open_queue(q)}
|
31
|
+
end
|
32
|
+
|
33
|
+
def dbmopen(dbname)
|
34
|
+
if @dbm == 'bdb'
|
35
|
+
BDB::Hash.new(dbname, nil, "a")
|
36
|
+
elsif @dbm == 'dbm'
|
37
|
+
DBM.open(dbname)
|
38
|
+
elsif @dbm == 'gdbm'
|
39
|
+
GDBM.open(dbname)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
|
44
|
+
def _open_queue(dest)
|
45
|
+
queue_name = dest.gsub('/', '_')
|
46
|
+
dbname = @directory + '/' + queue_name
|
47
|
+
@db[dest] = Hash.new
|
48
|
+
@db[dest][:dbh] = dbmopen(dbname)
|
49
|
+
@db[dest][:dbname] = dbname
|
50
|
+
end
|
51
|
+
|
52
|
+
|
53
|
+
def _close_queue(dest)
|
54
|
+
@db[dest][:dbh].close
|
55
|
+
dbname = @db[dest][:dbname]
|
56
|
+
File.delete(dbname) if File.exists?(dbname)
|
57
|
+
File.delete("#{dbname}.db") if File.exists?("#{dbname}.db")
|
58
|
+
File.delete("#{dbname}.pag") if File.exists?("#{dbname}.pag")
|
59
|
+
File.delete("#{dbname}.dir") if File.exists?("#{dbname}.dir")
|
60
|
+
end
|
61
|
+
|
62
|
+
def _writeframe(dest,frame,msgid)
|
63
|
+
@db[dest][:dbh][msgid] = Marshal::dump(frame)
|
64
|
+
end
|
65
|
+
|
66
|
+
def _readframe(dest,msgid)
|
67
|
+
frame_image = @db[dest][:dbh][msgid]
|
68
|
+
Marshal::load(frame_image)
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
#
|
2
|
+
#
|
3
|
+
#
|
4
|
+
module StompServer
|
5
|
+
#
|
6
|
+
# Low level physical queue handler.
|
7
|
+
#
|
8
|
+
class FileQueue < Queue
|
9
|
+
|
10
|
+
# Remove queue directory if it exists.
|
11
|
+
def _close_queue(dest)
|
12
|
+
Dir.delete(@queues[dest][:queue_dir]) if File.directory?(@queues[dest][:queue_dir])
|
13
|
+
end
|
14
|
+
|
15
|
+
# Create queue directory if it does not alrady exist.
|
16
|
+
def _open_queue(dest)
|
17
|
+
# handle clashes between _ and /
|
18
|
+
queue_name = dest.gsub('_','__')
|
19
|
+
queue_name = dest.gsub('/','_')
|
20
|
+
queue_dir = @directory + '/' + queue_name
|
21
|
+
@queues[dest][:queue_dir] = queue_dir
|
22
|
+
Dir.mkdir(queue_dir) unless File.directory?(queue_dir)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Write a messaage frame to the file system.
|
26
|
+
def _writeframe(dest,frame_todump,msgid)
|
27
|
+
filename = "#{@queues[dest][:queue_dir]}/#{msgid}"
|
28
|
+
frame = frame_todump.dup
|
29
|
+
frame_body = frame.body
|
30
|
+
frame.body = ''
|
31
|
+
frame_image = Marshal.dump(frame)
|
32
|
+
framelen = sprintf("%08x", frame_image.length)
|
33
|
+
bodylen = sprintf("%08x", frame_body.length)
|
34
|
+
File.open(filename,'wb') {|f| f.syswrite("#{framelen}#{bodylen}#{frame_image}#{frame_body}")}
|
35
|
+
return true
|
36
|
+
end
|
37
|
+
|
38
|
+
# Read a message frame from the file system.
|
39
|
+
def _readframe(dest,msgid)
|
40
|
+
filename = "#{@queues[dest][:queue_dir]}/#{msgid}"
|
41
|
+
file = nil
|
42
|
+
File.open(filename,'rb') {|f| file = f.read}
|
43
|
+
frame_len = file[0,8].hex
|
44
|
+
body_len = file[8,8].hex
|
45
|
+
frame = Marshal::load(file[16,frame_len])
|
46
|
+
frame.body = file[(frame_len + 16),body_len]
|
47
|
+
if File.delete(filename)
|
48
|
+
result = frame
|
49
|
+
else
|
50
|
+
result = false
|
51
|
+
end
|
52
|
+
return result
|
53
|
+
end
|
54
|
+
end # of class
|
55
|
+
end # of module
|
56
|
+
|
@@ -0,0 +1,64 @@
|
|
1
|
+
|
2
|
+
module StompServer
|
3
|
+
class MemoryQueue
|
4
|
+
attr_accessor :checkpoint_interval
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
|
8
|
+
@@log = Logger.new(STDOUT)
|
9
|
+
@@log.level = StompServer::LogHelper.get_loglevel()
|
10
|
+
|
11
|
+
@frame_index =0
|
12
|
+
@stompid = StompServer::StompId.new
|
13
|
+
@stats = Hash.new
|
14
|
+
@messages = Hash.new { Array.new }
|
15
|
+
@@log.debug "MemoryQueue initialized"
|
16
|
+
end
|
17
|
+
|
18
|
+
def stop(session_id)
|
19
|
+
@@log.debug("#{session_id} memory queue shutdown")
|
20
|
+
end
|
21
|
+
|
22
|
+
def monitor
|
23
|
+
stats = Hash.new
|
24
|
+
@messages.keys.each do |dest|
|
25
|
+
stats[dest] = {'size' => @messages[dest].size, 'enqueued' => @stats[dest][:enqueued], 'dequeued' => @stats[dest][:dequeued]}
|
26
|
+
end
|
27
|
+
stats
|
28
|
+
end
|
29
|
+
|
30
|
+
def dequeue(dest, session_id)
|
31
|
+
if frame = @messages[dest].shift
|
32
|
+
@stats[dest][:dequeued] += 1
|
33
|
+
return frame
|
34
|
+
else
|
35
|
+
return false
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def enqueue(dest,frame)
|
40
|
+
@frame_index += 1
|
41
|
+
if @stats[dest]
|
42
|
+
@stats[dest][:enqueued] += 1
|
43
|
+
else
|
44
|
+
@stats[dest] = Hash.new
|
45
|
+
@stats[dest][:enqueued] = 1
|
46
|
+
@stats[dest][:dequeued] = 0
|
47
|
+
end
|
48
|
+
assign_id(frame, dest)
|
49
|
+
requeue(dest, frame)
|
50
|
+
end
|
51
|
+
|
52
|
+
def requeue(dest,frame)
|
53
|
+
@messages[dest] += [frame]
|
54
|
+
end
|
55
|
+
|
56
|
+
def message_for?(dest, session_id)
|
57
|
+
!@messages[dest].empty?
|
58
|
+
end
|
59
|
+
|
60
|
+
def assign_id(frame, dest)
|
61
|
+
frame.headers['message-id'] = @stompid[@frame_index]
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,302 @@
|
|
1
|
+
#
|
2
|
+
# = QueueManager
|
3
|
+
#
|
4
|
+
# Used in conjunction with a storage class.
|
5
|
+
#
|
6
|
+
# The storage class MUST implement the following methods:
|
7
|
+
#
|
8
|
+
# * enqueue(queue name, frame)
|
9
|
+
#
|
10
|
+
# enqueue pushes a frame to the top of the queue in FIFO order. It's return
|
11
|
+
# value is ignored. enqueue must also set the message-id and add it to the
|
12
|
+
# frame header before inserting the frame into the queue.
|
13
|
+
#
|
14
|
+
# * dequeue(queue name)
|
15
|
+
#
|
16
|
+
# removes a frame from the bottom of the queue and returns it.
|
17
|
+
#
|
18
|
+
# * requeue(queue name,frame)
|
19
|
+
#
|
20
|
+
# does the same as enqueue, except it pushes the given frame to the
|
21
|
+
# bottom of the queue.
|
22
|
+
#
|
23
|
+
# The storage class MAY implement the following methods:
|
24
|
+
#
|
25
|
+
# * stop() method which should
|
26
|
+
#
|
27
|
+
# do any housekeeping that needs to be done before stompserver shuts down.
|
28
|
+
# stop() will be called when stompserver is shut down.
|
29
|
+
#
|
30
|
+
# * monitor() method which should
|
31
|
+
#
|
32
|
+
# return a hash of hashes containing the queue statistics.
|
33
|
+
# See the file queue for an example. Statistics are available to clients
|
34
|
+
# in /queue/monitor.
|
35
|
+
#
|
36
|
+
module StompServer
|
37
|
+
#
|
38
|
+
class QueueManager
|
39
|
+
Struct::new('QueueUser', :connection, :ack, :subid)
|
40
|
+
#
|
41
|
+
# Queue manager initialization.
|
42
|
+
#
|
43
|
+
def initialize(qstore)
|
44
|
+
@@log = Logger.new(STDOUT)
|
45
|
+
@@log.level = StompServer::LogHelper.get_loglevel()
|
46
|
+
@@log.debug("QM QueueManager initialize comletes")
|
47
|
+
#
|
48
|
+
@qstore = qstore
|
49
|
+
@queues = Hash.new { Array.new }
|
50
|
+
@pending = Hash.new
|
51
|
+
if $STOMP_SERVER
|
52
|
+
monitor = StompServer::QueueMonitor.new(@qstore,@queues)
|
53
|
+
monitor.start
|
54
|
+
@@log.debug "QM monitor started by QM initialization"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
#
|
58
|
+
# Server stop / shutdown.
|
59
|
+
#
|
60
|
+
def stop(session_id)
|
61
|
+
@qstore.stop(session_id) if (@qstore.methods.include?('stop') || @qstore.methods.include?(:stop))
|
62
|
+
end
|
63
|
+
#
|
64
|
+
# Client subscribe for a destination.
|
65
|
+
#
|
66
|
+
# Called from the protocol handler (subscribe method).
|
67
|
+
#
|
68
|
+
def subscribe(dest, connection, use_ack=false, subid = nil)
|
69
|
+
@@log.debug "#{connection.session_id} QM subscribe to #{dest}, ack => #{use_ack}, connection: #{connection}, subid: #{subid}"
|
70
|
+
user = Struct::QueueUser.new(connection, use_ack, subid)
|
71
|
+
@queues[dest] += [user]
|
72
|
+
send_destination_backlog(dest,user) unless dest == '/queue/monitor'
|
73
|
+
end
|
74
|
+
#
|
75
|
+
# send_a_backlog
|
76
|
+
#
|
77
|
+
# Send at most one frame to a connection.
|
78
|
+
# Used when use_ack == true.
|
79
|
+
# Called from the ack method.
|
80
|
+
#
|
81
|
+
def send_a_backlog(connection)
|
82
|
+
@@log.debug "#{connection.session_id} QM send_a_backlog starts"
|
83
|
+
#
|
84
|
+
# lookup queues with data for this connection
|
85
|
+
#
|
86
|
+
|
87
|
+
# :stopdoc:
|
88
|
+
|
89
|
+
# 1.9 compatability
|
90
|
+
#
|
91
|
+
# The Hash#select method returns:
|
92
|
+
#
|
93
|
+
# * An Array (of Arrays) in Ruby 1.8
|
94
|
+
# * A Hash in Ruby 1.9
|
95
|
+
#
|
96
|
+
# Watch the code in this method. It is a bit ugly because of that
|
97
|
+
# difference.
|
98
|
+
|
99
|
+
# :startdoc:
|
100
|
+
|
101
|
+
possible_queues = @queues.select{ |destination, users|
|
102
|
+
@qstore.message_for?(destination, connection.session_id) &&
|
103
|
+
users.detect{|u| u.connection == connection}
|
104
|
+
}
|
105
|
+
if possible_queues.empty?
|
106
|
+
@@log.debug "#{connection.session_id} QM s_a_b nothing to send"
|
107
|
+
return
|
108
|
+
end
|
109
|
+
#
|
110
|
+
# Get a random one (avoid artificial priority between queues
|
111
|
+
# without coding a whole scheduler, which might be desirable later)
|
112
|
+
#
|
113
|
+
# Select a random destination from those possible
|
114
|
+
|
115
|
+
# :stopdoc:
|
116
|
+
|
117
|
+
# Told ya' this would get ugly. A quote from the Pickaxe. I am:
|
118
|
+
#
|
119
|
+
# 'abandoning the benefits of polymorphism, and bringing the gods of refactoring down around my ears'
|
120
|
+
#
|
121
|
+
# :-)
|
122
|
+
|
123
|
+
# :startdoc:
|
124
|
+
|
125
|
+
# The following log call results in an exception using 1.9.2p180. I cannot
|
126
|
+
# recreate this using IRB. It has something to do with 'Struct's I think.
|
127
|
+
# @@log.debug("#{connection.session_id} possible_queues: #{possible_queues.inspect}")
|
128
|
+
|
129
|
+
|
130
|
+
case possible_queues
|
131
|
+
when Hash
|
132
|
+
# possible_queues _is_ a Hash
|
133
|
+
dests_possible = possible_queues.keys # Get keys of a Hash of destination / queues
|
134
|
+
dest_index = rand(dests_possible.size) # Random index
|
135
|
+
dest = dests_possible[dest_index] # Select a destination / queue
|
136
|
+
# The selected destination has (possibly) multiple users.
|
137
|
+
# Select a random user from those possible
|
138
|
+
user_index = rand(possible_queues[dest].size) # Random index
|
139
|
+
user = possible_queues[dest][user_index] # Array entry from Hash table entry
|
140
|
+
#
|
141
|
+
when Array
|
142
|
+
# possible_queues _is_ an Array
|
143
|
+
dest_index = rand(possible_queues.size) # Random index
|
144
|
+
dest_data = possible_queues[dest_index] # Select a destination + user array
|
145
|
+
dest = dest_data[0] # Select a destination / queue
|
146
|
+
# The selected destination has (possibly) multiple users.
|
147
|
+
# Select a random user from those possible
|
148
|
+
user_index = rand(dest_data[1].size) # Random index
|
149
|
+
user = dest_data[1][user_index] # Array entry from Hash table entry
|
150
|
+
else
|
151
|
+
raise "#{connection.session_id} something is very not right : #{RUBY_VERSION}"
|
152
|
+
end
|
153
|
+
|
154
|
+
#
|
155
|
+
@@log.debug "#{connection.session_id} QM s_a_b chosen -> dest: #{dest}"
|
156
|
+
# Ditto for this log statement using 1.9.2p180.
|
157
|
+
# @@log.debug "#{connection.session_id} QM s_a_b chosen -> user: #{user}"
|
158
|
+
#
|
159
|
+
frame = @qstore.dequeue(dest, connection.session_id)
|
160
|
+
send_to_user(frame, user)
|
161
|
+
end
|
162
|
+
#
|
163
|
+
# send_destination_backlog
|
164
|
+
#
|
165
|
+
# Called from the subscribe method.
|
166
|
+
#
|
167
|
+
def send_destination_backlog(dest,user)
|
168
|
+
@@log.debug "#{user.connection.session_id} QM send_destination_backlog for #{dest}"
|
169
|
+
if user.ack
|
170
|
+
# Only send one message, then wait for client ACK.
|
171
|
+
frame = @qstore.dequeue(dest, user.connection.session_id)
|
172
|
+
if frame
|
173
|
+
send_to_user(frame, user)
|
174
|
+
@@log.debug("#{user.connection.session_id} QM s_d_b single frame sent")
|
175
|
+
end
|
176
|
+
else
|
177
|
+
# Send all available messages.
|
178
|
+
while frame = @qstore.dequeue(dest, user.connection.session_id)
|
179
|
+
send_to_user(frame, user)
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
#
|
184
|
+
# Client unsubscribe.
|
185
|
+
#
|
186
|
+
# Called from the protocol handler (unsubscribe method).
|
187
|
+
#
|
188
|
+
def unsubscribe(dest, connection)
|
189
|
+
@@log.debug "#{connection.session_id} QM unsubscribe from #{dest}, connection #{connection}"
|
190
|
+
@queues.each do |d, queue|
|
191
|
+
queue.delete_if { |qu| qu.connection == connection and d == dest}
|
192
|
+
end
|
193
|
+
@queues.delete(dest) if @queues[dest].empty?
|
194
|
+
end
|
195
|
+
#
|
196
|
+
# Client ack.
|
197
|
+
#
|
198
|
+
# Called from the protocol handler (ack method).
|
199
|
+
#
|
200
|
+
def ack(connection, frame)
|
201
|
+
@@log.debug "#{connection.session_id} QM ACK."
|
202
|
+
@@log.debug "#{connection.session_id} QM ACK for frame: #{frame.inspect}"
|
203
|
+
unless @pending[connection]
|
204
|
+
@@log.debug "#{connection.session_id} QM No message pending for connection!"
|
205
|
+
return
|
206
|
+
end
|
207
|
+
msgid = frame.headers['message-id']
|
208
|
+
p_msgid = @pending[connection].headers['message-id']
|
209
|
+
if p_msgid != msgid
|
210
|
+
@@log.debug "#{connection.session_id} QM ACK Invalid message-id (received /#{msgid}/ != /#{p_msgid}/)"
|
211
|
+
# We don't know what happened, we requeue
|
212
|
+
# (probably a client connecting to a restarted server)
|
213
|
+
frame = @pending[connection]
|
214
|
+
@qstore.requeue(frame.headers['destination'],frame)
|
215
|
+
end
|
216
|
+
@pending.delete connection
|
217
|
+
# We are free to work now, look if there's something for us
|
218
|
+
send_a_backlog(connection)
|
219
|
+
end
|
220
|
+
#
|
221
|
+
# Client disconnect.
|
222
|
+
#
|
223
|
+
# Called from the protocol handler (unbind method).
|
224
|
+
#
|
225
|
+
def disconnect(connection)
|
226
|
+
@@log.debug("#{connection.session_id} QM DISCONNECT.")
|
227
|
+
frame = @pending[connection]
|
228
|
+
@@log.debug("#{connection.session_id} QM DISCONNECT pending frame: #{frame.inspect}")
|
229
|
+
if frame
|
230
|
+
@qstore.requeue(frame.headers['destination'],frame)
|
231
|
+
@pending.delete connection
|
232
|
+
end
|
233
|
+
#
|
234
|
+
@queues.each do |dest, queue|
|
235
|
+
queue.delete_if { |qu| qu.connection == connection }
|
236
|
+
@queues.delete(dest) if queue.empty?
|
237
|
+
end
|
238
|
+
end
|
239
|
+
#
|
240
|
+
# send_to_user
|
241
|
+
#
|
242
|
+
def send_to_user(frame, user)
|
243
|
+
@@log.debug("#{user.connection.session_id} QM send_to_user")
|
244
|
+
connection = user.connection
|
245
|
+
frame.headers['subscription'] = user.subid if user.subid
|
246
|
+
if user.ack
|
247
|
+
# raise on internal logic error.
|
248
|
+
raise "#{user.connection.session_id} other connection's end already busy" if @pending[connection]
|
249
|
+
# A maximum of one frame can be pending ACK.
|
250
|
+
@pending[connection] = frame
|
251
|
+
end
|
252
|
+
connection.stomp_send_data(frame)
|
253
|
+
end
|
254
|
+
#
|
255
|
+
# sendmsg
|
256
|
+
#
|
257
|
+
# Called from the protocol handler (sendmsg method, process_frame method).
|
258
|
+
#
|
259
|
+
def sendmsg(frame)
|
260
|
+
#
|
261
|
+
@@log.debug("#{frame.headers['session']} QM client SEND Processing, #{frame}")
|
262
|
+
frame.command = "MESSAGE"
|
263
|
+
dest = frame.headers['destination']
|
264
|
+
# Lookup a user willing to handle this destination
|
265
|
+
available_users = @queues[dest].reject{|user| @pending[user.connection]}
|
266
|
+
if available_users.empty?
|
267
|
+
@qstore.enqueue(dest,frame)
|
268
|
+
return
|
269
|
+
end
|
270
|
+
#
|
271
|
+
# Look for a user with ack (we favor reliability)
|
272
|
+
#
|
273
|
+
reliable_user = available_users.find{|u| u.ack}
|
274
|
+
#
|
275
|
+
if reliable_user
|
276
|
+
# give it a message-id
|
277
|
+
@qstore.assign_id(frame, dest)
|
278
|
+
send_to_user(frame, reliable_user)
|
279
|
+
else
|
280
|
+
random_user = available_users[rand(available_users.length)]
|
281
|
+
# Note message-id header isn't set but we won't need it anyway
|
282
|
+
# <TODO> could break some clients: fix this
|
283
|
+
send_to_user(frame, random_user)
|
284
|
+
end
|
285
|
+
end
|
286
|
+
#
|
287
|
+
# dequeue: remove a message from a queue.
|
288
|
+
#
|
289
|
+
def dequeue(dest, session_id)
|
290
|
+
@qstore.dequeue(dest, session_id)
|
291
|
+
end
|
292
|
+
#
|
293
|
+
# enqueue: add a message to a queue.
|
294
|
+
#
|
295
|
+
def enqueue(frame)
|
296
|
+
frame.command = "MESSAGE"
|
297
|
+
dest = frame.headers['destination']
|
298
|
+
@qstore.enqueue(dest,frame)
|
299
|
+
end
|
300
|
+
end # of class
|
301
|
+
end # of module
|
302
|
+
|