stompserver 0.9.1
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +10 -0
- data/Manifest.txt +16 -0
- data/README.txt +58 -0
- data/Rakefile +23 -0
- data/bin/stompserver +0 -0
- data/lib/frame_journal.rb +134 -0
- data/lib/queue_manager.rb +68 -0
- data/lib/stomp_frame.rb +92 -0
- data/lib/stomp_server.rb +156 -0
- data/lib/topic_manager.rb +24 -0
- data/setup.rb +1585 -0
- data/test/test_frame_journal.rb +14 -0
- data/test/test_queue_manager.rb +83 -0
- data/test/test_stomp_frame.rb +121 -0
- data/test/test_stomp_server.rb +241 -0
- data/test/test_topic_manager.rb +68 -0
- metadata +88 -0
data/History.txt
ADDED
data/Manifest.txt
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
History.txt
|
2
|
+
Manifest.txt
|
3
|
+
README.txt
|
4
|
+
Rakefile
|
5
|
+
setup.rb
|
6
|
+
bin/stompserver
|
7
|
+
lib/frame_journal.rb
|
8
|
+
lib/queue_manager.rb
|
9
|
+
lib/stomp_frame.rb
|
10
|
+
lib/stomp_server.rb
|
11
|
+
lib/topic_manager.rb
|
12
|
+
test/test_frame_journal.rb
|
13
|
+
test/test_queue_manager.rb
|
14
|
+
test/test_stomp_frame.rb
|
15
|
+
test/test_stomp_server.rb
|
16
|
+
test/test_topic_manager.rb
|
data/README.txt
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
stompserver
|
2
|
+
by Patrick Hurley
|
3
|
+
http://stompserver.rubyforge.org/
|
4
|
+
|
5
|
+
== DESCRIPTION:
|
6
|
+
|
7
|
+
Don't want to install a JVM, but still want to use messaging? Me too,
|
8
|
+
so I threw together this little server. All the hard work was done
|
9
|
+
by Francis Cianfrocca (big thank you) in his event machine gem (which
|
10
|
+
is required by this server).
|
11
|
+
|
12
|
+
== FEATURES/PROBLEMS:
|
13
|
+
|
14
|
+
Handles basic message queue processing
|
15
|
+
Does not support any server to server messaging
|
16
|
+
(although you could write a client to do this)
|
17
|
+
Server Id is not being well initialized
|
18
|
+
Quite a bit of polish is still required to make into a daemon/service
|
19
|
+
and add command line handling.
|
20
|
+
And oh yeah, I need to write some docs (see the tests for now)
|
21
|
+
|
22
|
+
== SYNOPSYS:
|
23
|
+
|
24
|
+
Handles basic message queue processing
|
25
|
+
|
26
|
+
== REQUIREMENTS:
|
27
|
+
|
28
|
+
+ EventMachine
|
29
|
+
+ madeleine
|
30
|
+
|
31
|
+
== INSTALL:
|
32
|
+
|
33
|
+
+ Grab the gem
|
34
|
+
|
35
|
+
== LICENSE:
|
36
|
+
|
37
|
+
(The MIT License)
|
38
|
+
|
39
|
+
Copyright (c) 2006 Patrick Hurley
|
40
|
+
|
41
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
42
|
+
a copy of this software and associated documentation files (the
|
43
|
+
'Software'), to deal in the Software without restriction, including
|
44
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
45
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
46
|
+
permit persons to whom the Software is furnished to do so, subject to
|
47
|
+
the following conditions:
|
48
|
+
|
49
|
+
The above copyright notice and this permission notice shall be
|
50
|
+
included in all copies or substantial portions of the Software.
|
51
|
+
|
52
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
53
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
54
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
55
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
56
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
57
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
58
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'hoe'
|
5
|
+
$LOAD_PATH << "./lib"
|
6
|
+
require 'stomp_server'
|
7
|
+
|
8
|
+
Hoe.new('stompserver', StompServer::VERSION) do |p|
|
9
|
+
p.rubyforge_name = 'stompserver'
|
10
|
+
p.summary = 'A very light messaging server'
|
11
|
+
p.description = p.paragraphs_of('README.txt', 2..4).join("\n\n")
|
12
|
+
p.url = p.paragraphs_of('README.txt', 0).first.split(/\n/)[1..-1]
|
13
|
+
p.changes = p.paragraphs_of('History.txt', 0..1).join("\n\n")
|
14
|
+
p.email = "phurley@gmail.com"
|
15
|
+
p.author = ["Patrick Hurley"]
|
16
|
+
p.extra_deps = [
|
17
|
+
["eventmachine", ">= 5.0.0"],
|
18
|
+
["madeleine", ">= 0.7.3"],
|
19
|
+
["hoe", ">= 1.1.1"]
|
20
|
+
]
|
21
|
+
end
|
22
|
+
|
23
|
+
# vim: syntax=Ruby
|
data/bin/stompserver
ADDED
File without changes
|
@@ -0,0 +1,134 @@
|
|
1
|
+
# Simple Journal File(s) Manager
|
2
|
+
# You select the directory, and it will collect the messages
|
3
|
+
# The journal file format is:
|
4
|
+
#
|
5
|
+
# Status Byte: 0 - pending, 1 - processed
|
6
|
+
# Frame Size: 4 byte long (network endian - yes I limit my messages to 4G)
|
7
|
+
# Message
|
8
|
+
#
|
9
|
+
# Repeat
|
10
|
+
#
|
11
|
+
# When the size of a journal file exceeds its limit
|
12
|
+
|
13
|
+
require 'madeleine'
|
14
|
+
require 'madeleine/automatic'
|
15
|
+
|
16
|
+
class MadFrameJournal
|
17
|
+
include Madeleine::Automatic::Interceptor
|
18
|
+
attr_reader :frames
|
19
|
+
automatic_read_only :frames
|
20
|
+
attr_accessor :frame_index
|
21
|
+
automatic_read_only :frame_index
|
22
|
+
attr_accessor :system_id
|
23
|
+
automatic_read_only :system_id
|
24
|
+
|
25
|
+
def initialize
|
26
|
+
@frames = {}
|
27
|
+
@frame_index = 0
|
28
|
+
@system_id = nil
|
29
|
+
end
|
30
|
+
|
31
|
+
def add(msgid, frame)
|
32
|
+
@frames[msgid] = frame
|
33
|
+
end
|
34
|
+
|
35
|
+
def delete(msgid)
|
36
|
+
@frames.delete(msgid)
|
37
|
+
end
|
38
|
+
|
39
|
+
def clear
|
40
|
+
@frames.clear
|
41
|
+
end
|
42
|
+
|
43
|
+
automatic_read_only :lookup
|
44
|
+
def lookup(msgid)
|
45
|
+
@frames[msgid]
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
class FrameJournal
|
50
|
+
def initialize(directory='frame-journal', snap_freq = 60 * 5)
|
51
|
+
@directory = directory
|
52
|
+
@mad = AutomaticSnapshotMadeleine.new(directory) do
|
53
|
+
MadFrameJournal.new
|
54
|
+
end
|
55
|
+
|
56
|
+
# always snap on startup, in case we had an previous failure
|
57
|
+
@modified = true
|
58
|
+
Thread.new(@mad, snap_freq) do |mad, freq|
|
59
|
+
while true
|
60
|
+
sleep(freq)
|
61
|
+
mad.take_snapshot if @modified
|
62
|
+
@modified = false
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def []=(msgid, frame)
|
68
|
+
@modified = true
|
69
|
+
@mad.system.add(msgid, frame)
|
70
|
+
end
|
71
|
+
|
72
|
+
def [](msgid)
|
73
|
+
@mad.system.lookup(msgid)
|
74
|
+
end
|
75
|
+
|
76
|
+
def delete(msgid)
|
77
|
+
@modified = true
|
78
|
+
@mad.system.delete(msgid)
|
79
|
+
end
|
80
|
+
|
81
|
+
def keys
|
82
|
+
@mad.system.frames.keys
|
83
|
+
end
|
84
|
+
|
85
|
+
def clear
|
86
|
+
@modified = true
|
87
|
+
@mad.system.clear
|
88
|
+
@mad.take_snapshot
|
89
|
+
end
|
90
|
+
|
91
|
+
def index
|
92
|
+
@mad.system.frame_index
|
93
|
+
end
|
94
|
+
|
95
|
+
def next_index
|
96
|
+
@modified = true
|
97
|
+
@mad.system.frame_index += 1
|
98
|
+
end
|
99
|
+
|
100
|
+
def system_id
|
101
|
+
unless name = @mad.system.system_id
|
102
|
+
# todo - grab default name from some place smarter...
|
103
|
+
@modified = true
|
104
|
+
@mad.system.system_id = 'cmastomp'
|
105
|
+
name = @mad.system.system_id
|
106
|
+
end
|
107
|
+
name
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
if __FILE__ == $0
|
112
|
+
fj = FrameJournal.new('fj', 3)
|
113
|
+
until ARGV.empty?
|
114
|
+
case cmd = ARGV.shift
|
115
|
+
when "keys"
|
116
|
+
puts fj.keys.inspect
|
117
|
+
when "dump"
|
118
|
+
fj.keys.each do |key|
|
119
|
+
puts "#{key}: #{fj[key]}"
|
120
|
+
end
|
121
|
+
when "show"
|
122
|
+
key = ARGV.shift
|
123
|
+
puts "#{key}: #{fj[key]}"
|
124
|
+
when "add"
|
125
|
+
key = ARGV.shift
|
126
|
+
val = ARGV.shift
|
127
|
+
fj[key] = val
|
128
|
+
when "sleep"
|
129
|
+
sleep ARGV.shift.to_i
|
130
|
+
when "delete"
|
131
|
+
fj.delete(ARGV.shift)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# queue - persistent sent to a single subscriber
|
2
|
+
# queue_monitor - looks, but does not remove from queue
|
3
|
+
|
4
|
+
class QueueManager
|
5
|
+
Struct::new('QueueUser', :user, :ack)
|
6
|
+
|
7
|
+
def initialize(journal)
|
8
|
+
# read journal information
|
9
|
+
@journal = journal
|
10
|
+
@queues = Hash.new { Array.new }
|
11
|
+
@pending = Hash.new { Array.new }
|
12
|
+
@messages = Hash.new { Array.new }
|
13
|
+
|
14
|
+
# recover from previous run
|
15
|
+
msgids = @journal.keys.sort
|
16
|
+
msgids.each do |msgid|
|
17
|
+
sendmsg(@journal[msgid])
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def subscribe(dest, user, use_ack=false)
|
22
|
+
user = Struct::QueueUser.new(user, use_ack)
|
23
|
+
@queues[dest] += [user]
|
24
|
+
@messages[dest].each do |frame|
|
25
|
+
send_to_user(frame, user)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def unsubscribe(topic, user)
|
30
|
+
@queues[topic].delete_if { |u| u.user == user }
|
31
|
+
end
|
32
|
+
|
33
|
+
def ack(user, frame)
|
34
|
+
pending_size = @pending[user]
|
35
|
+
msgid = frame.headers['message-id']
|
36
|
+
@pending[user].delete_if { |pf| pf.headers['message-id'] == msgid }
|
37
|
+
raise "Message (#{msgid}) not found" if pending_size == @pending[user]
|
38
|
+
@journal.delete(msgid)
|
39
|
+
end
|
40
|
+
|
41
|
+
def disconnect(user)
|
42
|
+
@pending[user].each do |frame|
|
43
|
+
sendmsg(frame)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def send_to_user(frame, user)
|
48
|
+
if user.ack
|
49
|
+
@pending[user.user] += [frame]
|
50
|
+
else
|
51
|
+
@journal.delete(frame.headers['message-id'])
|
52
|
+
end
|
53
|
+
user.user.send_data(frame.to_s)
|
54
|
+
end
|
55
|
+
|
56
|
+
def sendmsg(frame)
|
57
|
+
frame.command = "MESSAGE"
|
58
|
+
dest = frame.headers['destination']
|
59
|
+
@journal[frame.headers['message-id']] = frame
|
60
|
+
|
61
|
+
if user = @queues[dest].shift
|
62
|
+
send_to_user(frame, user)
|
63
|
+
@queues[dest].push(user)
|
64
|
+
else
|
65
|
+
@messages[dest] += [frame]
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
data/lib/stomp_frame.rb
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
class StompFrame
|
2
|
+
attr_accessor :command, :headers, :body
|
3
|
+
def initialize(command=nil, headers=nil, body=nil)
|
4
|
+
@command = command
|
5
|
+
@headers = headers || {}
|
6
|
+
@body = body || ''
|
7
|
+
end
|
8
|
+
|
9
|
+
def to_s
|
10
|
+
result = @command + "\n"
|
11
|
+
@headers['content-length'] = @body.size.to_s if @body.include?(0)
|
12
|
+
@headers.each_pair do |key, value|
|
13
|
+
result << "#{key}:#{value}\n"
|
14
|
+
end
|
15
|
+
result << "\n"
|
16
|
+
result << @body.to_s
|
17
|
+
result << "\000\n"
|
18
|
+
end
|
19
|
+
|
20
|
+
def dest
|
21
|
+
#@dest || (@dest = @headers['destination'])
|
22
|
+
@headers['destination']
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class StompFrameRecognizer
|
27
|
+
attr_accessor :frames
|
28
|
+
|
29
|
+
def initialize
|
30
|
+
@buffer = ''
|
31
|
+
@body_length = nil
|
32
|
+
@frame = StompFrame.new
|
33
|
+
@frames = []
|
34
|
+
end
|
35
|
+
|
36
|
+
def parse_body(len)
|
37
|
+
raise RuntimeError.new("Invalid stompframe (missing null term)") unless @buffer[len] == 0
|
38
|
+
@frame.body = @buffer[0...len]
|
39
|
+
@buffer = @buffer[len+1..-1]
|
40
|
+
@frames << @frame
|
41
|
+
@frame = StompFrame.new
|
42
|
+
end
|
43
|
+
|
44
|
+
def parse_binary_body
|
45
|
+
if @buffer.length > @body_length
|
46
|
+
parse_body(@body_length)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def parse_text_body
|
51
|
+
if pos = @buffer.index(0)
|
52
|
+
parse_body(pos)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def parse_header
|
57
|
+
if match = @buffer.match(/^\s*(\S+)$((?:\s*.*?\s*:\s*.*?\s*$)*)$\r?\n$\r?\n/)
|
58
|
+
@frame.command, headers = match.captures
|
59
|
+
@buffer = match.post_match
|
60
|
+
headers.split(/\n/).each do |data|
|
61
|
+
if data =~ /^\s*(\S+)\s*:\s*(.*?)\s*$/
|
62
|
+
@frame.headers[$1] = $2
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# body_length is nil, if there is no content-length, otherwise it is the length (as in integer)
|
67
|
+
@body_length = @frame.headers['content-length'] && @frame.headers['content-length'].to_i
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def parse
|
72
|
+
count = @frames.size
|
73
|
+
|
74
|
+
parse_header unless @frame.command
|
75
|
+
if @frame.command
|
76
|
+
if @body_length
|
77
|
+
parse_binary_body
|
78
|
+
else
|
79
|
+
parse_text_body
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# parse_XXX_body return the frame if they succeed and nil if they fail
|
84
|
+
# the result will fall through
|
85
|
+
parse if count != @frames.size
|
86
|
+
end
|
87
|
+
|
88
|
+
def<< (buf)
|
89
|
+
@buffer << buf
|
90
|
+
parse
|
91
|
+
end
|
92
|
+
end
|
data/lib/stomp_server.rb
ADDED
@@ -0,0 +1,156 @@
|
|
1
|
+
require 'eventmachine'
|
2
|
+
require 'stomp_frame'
|
3
|
+
require 'topic_manager'
|
4
|
+
require 'queue_manager'
|
5
|
+
require 'frame_journal'
|
6
|
+
|
7
|
+
module StompServer
|
8
|
+
VERSION = '0.9.1'
|
9
|
+
VALID_COMMANDS = [:connect, :send, :subscribe, :unsubscribe, :begin, :commit, :abort, :ack, :disconnect]
|
10
|
+
|
11
|
+
def self.setup(j = FrameJournal.new, tm = TopicManager.new, qm = QueueManager.new(j))
|
12
|
+
@@journal = j
|
13
|
+
@@topic_manager = tm
|
14
|
+
@@queue_manager = qm
|
15
|
+
end
|
16
|
+
|
17
|
+
def post_init
|
18
|
+
@sfr = StompFrameRecognizer.new
|
19
|
+
@transactions = {}
|
20
|
+
@connected = false
|
21
|
+
end
|
22
|
+
|
23
|
+
def receive_data(data)
|
24
|
+
begin
|
25
|
+
puts "receive_data: #{data.inspect}" if $DEBUG
|
26
|
+
@sfr << data
|
27
|
+
process_frames
|
28
|
+
rescue Exception => e
|
29
|
+
puts "err: #{e} #{e.backtrace.join("\n")}" if $DEBUG
|
30
|
+
send_error(e.to_s)
|
31
|
+
close_connection_after_writing
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def process_frames
|
36
|
+
frame = nil
|
37
|
+
process_frame(frame) while frame = @sfr.frames.shift
|
38
|
+
end
|
39
|
+
|
40
|
+
def process_frame(frame)
|
41
|
+
cmd = frame.command.downcase.to_sym
|
42
|
+
raise "Unhandled frame: #{cmd}" unless VALID_COMMANDS.include?(cmd)
|
43
|
+
raise "Not connected" if !@connected && cmd != :connect
|
44
|
+
|
45
|
+
# I really like this code, but my needs are a little trickier
|
46
|
+
#
|
47
|
+
|
48
|
+
if trans = frame.headers['transaction']
|
49
|
+
handle_transaction(frame, trans, cmd)
|
50
|
+
else
|
51
|
+
cmd = :sendmsg if cmd == :send
|
52
|
+
send(cmd, frame)
|
53
|
+
end
|
54
|
+
|
55
|
+
send_receipt(frame.headers['receipt']) if frame.headers['receipt']
|
56
|
+
end
|
57
|
+
|
58
|
+
def handle_transaction(frame, trans, cmd)
|
59
|
+
if [:begin, :commit, :abort].include?(cmd)
|
60
|
+
send(cmd, frame, trans)
|
61
|
+
else
|
62
|
+
raise "transaction does not exist" unless @transactions.has_key?(trans)
|
63
|
+
@transactions[trans] << frame
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def connect(frame)
|
68
|
+
puts "Connecting" if $DEBUG
|
69
|
+
response = StompFrame.new("CONNECTED", {'session' => 'wow'})
|
70
|
+
send_data(response.to_s)
|
71
|
+
@connected = true
|
72
|
+
end
|
73
|
+
|
74
|
+
def sendmsg(frame)
|
75
|
+
# set message id
|
76
|
+
frame.headers['message-id'] = "msg-#{@@journal.system_id}-#{@@journal.next_index}"
|
77
|
+
if frame.dest.match(%r|^/queue|)
|
78
|
+
@@queue_manager.sendmsg(frame)
|
79
|
+
else
|
80
|
+
@@topic_manager.sendmsg(frame)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def subscribe(frame)
|
85
|
+
if frame.dest =~ %r|^/queue|
|
86
|
+
@@queue_manager.subscribe(frame.dest, self)
|
87
|
+
else
|
88
|
+
@@topic_manager.subscribe(frame.dest, self)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def unsubscribe(frame)
|
93
|
+
if frame.dest =~ %r|^/queue|
|
94
|
+
@@queue_manager.unsubscribe(self)
|
95
|
+
else
|
96
|
+
@@topic_manager.unsubscribe(self)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def begin(frame, trans=nil)
|
101
|
+
raise "Missing transaction" unless trans
|
102
|
+
raise "transaction exists" if @transactions.has_key?(trans)
|
103
|
+
@transactions[trans] = []
|
104
|
+
end
|
105
|
+
|
106
|
+
def commit(frame, trans=nil)
|
107
|
+
raise "Missing transaction" unless trans
|
108
|
+
raise "transaction does not exist" unless @transactions.has_key?(trans)
|
109
|
+
|
110
|
+
(@transactions[trans]).each do |frame|
|
111
|
+
frame.headers.delete('transaction')
|
112
|
+
process_frame(frame)
|
113
|
+
end
|
114
|
+
@transactions.delete(trans)
|
115
|
+
end
|
116
|
+
|
117
|
+
def abort(frame, trans=nil)
|
118
|
+
raise "Missing transaction" unless trans
|
119
|
+
raise "transaction does not exist" unless @transactions.has_key?(trans)
|
120
|
+
@transactions.delete(trans)
|
121
|
+
end
|
122
|
+
|
123
|
+
def ack(frame)
|
124
|
+
@@queue_manager.ack(self, frame)
|
125
|
+
end
|
126
|
+
|
127
|
+
def disconnect(frame)
|
128
|
+
puts "Polite disconnect" if $DEBUG
|
129
|
+
close_connection_after_writing
|
130
|
+
end
|
131
|
+
|
132
|
+
def send_message(msg)
|
133
|
+
msg.command = "MESSAGE"
|
134
|
+
send_data(msg.to_s)
|
135
|
+
end
|
136
|
+
|
137
|
+
def send_receipt(id)
|
138
|
+
send_frame("RECEIPT", { 'receipt-id' => id})
|
139
|
+
end
|
140
|
+
|
141
|
+
def send_error(msg)
|
142
|
+
send_frame("ERROR",{},msg)
|
143
|
+
end
|
144
|
+
|
145
|
+
def send_frame(command, headers={}, body='')
|
146
|
+
response = StompFrame.new(command, headers, body)
|
147
|
+
send_data(response.to_s)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
if $0 == __FILE__
|
152
|
+
StompServer.setup
|
153
|
+
EventMachine::run do
|
154
|
+
EventMachine.start_server "0.0.0.0", 61613, StompServer
|
155
|
+
end
|
156
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# topic - non persistent, sent to all interested parties
|
2
|
+
|
3
|
+
class TopicManager
|
4
|
+
def initialize
|
5
|
+
@topics = Hash.new { Array.new }
|
6
|
+
end
|
7
|
+
|
8
|
+
def subscribe(topic, user)
|
9
|
+
@topics[topic] += [user]
|
10
|
+
end
|
11
|
+
|
12
|
+
def unsubscribe(topic, user)
|
13
|
+
@topics[topic].delete(user)
|
14
|
+
end
|
15
|
+
|
16
|
+
def sendmsg(msg)
|
17
|
+
msg.command = "MESSAGE"
|
18
|
+
topic = msg.headers['destination']
|
19
|
+
payload = msg.to_s
|
20
|
+
@topics[topic].each do |user|
|
21
|
+
user.send_data(payload)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|