stompserver_ng 1.0.6
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|