stompserver 0.9.1
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 +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
|