stompserver 0.9.7 → 0.9.8

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,128 @@
1
+
2
+ class Mongrel::HttpRequest
3
+ attr_reader :body, :params
4
+
5
+ def initialize(params, initial_body)
6
+ @params = params
7
+ @body = StringIO.new
8
+ @body.write params.http_body
9
+ end
10
+ end
11
+
12
+ module StompServer
13
+ module StompServer::Protocols
14
+
15
+ class Http < EventMachine::Connection
16
+
17
+ def initialize *args
18
+ super
19
+ @buf = ''
20
+ end
21
+
22
+
23
+ def post_init
24
+ @parser = Mongrel::HttpParser.new
25
+ @params = Mongrel::HttpParams.new
26
+ @nparsed = 0
27
+ @request = nil
28
+ @request_method = nil
29
+ @request_length = 0
30
+ @state = :headers
31
+ @headers_out = {'Content-Length' => 0, 'Content-Type' => 'text/plain; charset=UTF-8'}
32
+ end
33
+
34
+ def receive_data data
35
+ parse_request(data)
36
+ end
37
+
38
+ def parse_request data
39
+ @buf << data
40
+ case @state
41
+ when :headers
42
+ @nparsed = @parser.execute(@params, @buf, @nparsed)
43
+ if @parser.finished?
44
+ @request = Mongrel::HttpRequest.new(@params,@buf)
45
+ @request_method = @request.params[Mongrel::Const::REQUEST_METHOD]
46
+ content_length = @request.params[Mongrel::Const::CONTENT_LENGTH].to_i
47
+ @request_length = @nparsed + content_length
48
+ @remain = content_length - @request.params.http_body.length
49
+ if @remain <= 0
50
+ @buf = @buf[@request_length+1..-1] || ''
51
+ process_request
52
+ post_init
53
+ return
54
+ end
55
+ @request.body.write @request.params.http_body
56
+ @state = :body
57
+ end
58
+ when :body
59
+ @remain -= @request.body.write data[0...@remain]
60
+ if @remain <= 0
61
+ @buf = @buf[@request_length+1..-1] || ''
62
+ process_request
63
+ post_init
64
+ return
65
+ end
66
+ end
67
+ end
68
+
69
+ def process_request
70
+ begin
71
+ @request.body.rewind
72
+ dest = @request.params[Mongrel::Const::REQUEST_PATH]
73
+ case @request_method
74
+ when 'PUT'
75
+ @frame = StompServer::StompFrame.new
76
+ @frame.command = 'SEND'
77
+ @frame.body = @request.body.read
78
+ @frame.headers['destination'] = dest
79
+ if @@queue_manager.enqueue(@frame)
80
+ create_response('200','Message Enqueued')
81
+ else
82
+ create_response('500','Error enqueueing message')
83
+ end
84
+ when 'GET'
85
+ if frame = @@queue_manager.dequeue(dest)
86
+ @headers_out['message-id'] = frame.headers['message-id']
87
+ create_response('200',frame.body)
88
+ else
89
+ create_response('404','No messages in queue')
90
+ end
91
+ else
92
+ create_response('500','Invalid Command')
93
+ end
94
+ rescue Exception => e
95
+ puts "err: #{e} #{e.backtrace.join("\n")}"
96
+ create_response('500',e)
97
+ end
98
+ end
99
+
100
+ def unbind
101
+ puts "Closing connection"
102
+ close_connection_after_writing
103
+ end
104
+
105
+ def create_response(code,response_text)
106
+ response = ''
107
+ @headers_out['Content-Length'] = response_text.size
108
+
109
+ case code
110
+ when '200'
111
+ response << "HTTP/1.1 200 OK\r\n"
112
+ when '500'
113
+ response << "HTTP/1.1 500 Server Error\r\n"
114
+ when '404'
115
+ response << "HTTP/1.1 404 Message Not Found\r\n"
116
+ end
117
+ @headers_out.each_pair do |key, value|
118
+ response << "#{key}:#{value}\r\n"
119
+ end
120
+ response << "\r\n"
121
+ response << response_text
122
+ send_data(response)
123
+ unbind if @request.params['HTTP_CONNECTION'] == 'close'
124
+ end
125
+ end
126
+
127
+ end
128
+ end
@@ -0,0 +1,186 @@
1
+
2
+ module StompServer
3
+ module StompServer::Protocols
4
+ VALID_COMMANDS = [:connect, :send, :subscribe, :unsubscribe, :begin, :commit, :abort, :ack, :disconnect]
5
+
6
+ class Stomp < EventMachine::Connection
7
+
8
+ def initialize *args
9
+ super
10
+ end
11
+
12
+ def post_init
13
+ @sfr = StompServer::StompFrameRecognizer.new
14
+ @transactions = {}
15
+ @connected = false
16
+ end
17
+
18
+ def receive_data(data)
19
+ stomp_receive_data(data)
20
+ end
21
+
22
+ def stomp_receive_data(data)
23
+ begin
24
+ puts "receive_data: #{data.inspect}" if $DEBUG
25
+ @sfr << data
26
+ process_frames
27
+ rescue Exception => e
28
+ puts "err: #{e} #{e.backtrace.join("\n")}"
29
+ send_error(e.to_s)
30
+ close_connection_after_writing
31
+ end
32
+ end
33
+
34
+ def stomp_receive_frame(frame)
35
+ begin
36
+ puts "receive_frame: #{frame.inspect}" if $DEBUG
37
+ process_frame(frame)
38
+ rescue Exception => e
39
+ puts "err: #{e} #{e.backtrace.join("\n")}"
40
+ send_error(e.to_s)
41
+ close_connection_after_writing
42
+ end
43
+ end
44
+
45
+ def process_frames
46
+ frame = nil
47
+ process_frame(frame) while frame = @sfr.frames.shift
48
+ end
49
+
50
+ def process_frame(frame)
51
+ cmd = frame.command.downcase.to_sym
52
+ raise "Unhandled frame: #{cmd}" unless VALID_COMMANDS.include?(cmd)
53
+ raise "Not connected" if !@connected && cmd != :connect
54
+
55
+ # I really like this code, but my needs are a little trickier
56
+ #
57
+
58
+ if trans = frame.headers['transaction']
59
+ handle_transaction(frame, trans, cmd)
60
+ else
61
+ cmd = :sendmsg if cmd == :send
62
+ send(cmd, frame)
63
+ end
64
+
65
+ send_receipt(frame.headers['receipt']) if frame.headers['receipt']
66
+ end
67
+
68
+ def handle_transaction(frame, trans, cmd)
69
+ if [:begin, :commit, :abort].include?(cmd)
70
+ send(cmd, frame, trans)
71
+ else
72
+ raise "transaction does not exist" unless @transactions.has_key?(trans)
73
+ @transactions[trans] << frame
74
+ end
75
+ end
76
+
77
+ def connect(frame)
78
+ if @@auth_required
79
+ unless frame.headers['login'] and frame.headers['passcode'] and @@stompauth.authorized[frame.headers['login']] == frame.headers['passcode']
80
+ raise "Invalid Login"
81
+ end
82
+ end
83
+ puts "Connecting" if $DEBUG
84
+ response = StompServer::StompFrame.new("CONNECTED", {'session' => 'wow'})
85
+ stomp_send_data(response)
86
+ @connected = true
87
+ end
88
+
89
+ def sendmsg(frame)
90
+ # set message id
91
+ if frame.dest.match(%r|^/queue|)
92
+ @@queue_manager.sendmsg(frame)
93
+ else
94
+ frame.headers['message-id'] = "msg-#stompcma-#{@@topic_manager.next_index}"
95
+ @@topic_manager.sendmsg(frame)
96
+ end
97
+ end
98
+
99
+ def subscribe(frame)
100
+ use_ack = false
101
+ use_ack = true if frame.headers['ack'] == 'client'
102
+ if frame.dest =~ %r|^/queue|
103
+ @@queue_manager.subscribe(frame.dest, self,use_ack)
104
+ else
105
+ @@topic_manager.subscribe(frame.dest, self)
106
+ end
107
+ end
108
+
109
+ def unsubscribe(frame)
110
+ if frame.dest =~ %r|^/queue|
111
+ @@queue_manager.unsubscribe(frame.dest,self)
112
+ else
113
+ @@topic_manager.unsubscribe(frame.dest,self)
114
+ end
115
+ end
116
+
117
+ def begin(frame, trans=nil)
118
+ raise "Missing transaction" unless trans
119
+ raise "transaction exists" if @transactions.has_key?(trans)
120
+ @transactions[trans] = []
121
+ end
122
+
123
+ def commit(frame, trans=nil)
124
+ raise "Missing transaction" unless trans
125
+ raise "transaction does not exist" unless @transactions.has_key?(trans)
126
+
127
+ (@transactions[trans]).each do |frame|
128
+ frame.headers.delete('transaction')
129
+ process_frame(frame)
130
+ end
131
+ @transactions.delete(trans)
132
+ end
133
+
134
+ def abort(frame, trans=nil)
135
+ raise "Missing transaction" unless trans
136
+ raise "transaction does not exist" unless @transactions.has_key?(trans)
137
+ @transactions.delete(trans)
138
+ end
139
+
140
+ def ack(frame)
141
+ @@queue_manager.ack(self, frame)
142
+ end
143
+
144
+ def disconnect(frame)
145
+ puts "Polite disconnect" if $DEBUG
146
+ close_connection_after_writing
147
+ end
148
+
149
+ def unbind
150
+ p "Unbind called" if $DEBUG
151
+ @connected = false
152
+ @@queue_manager.disconnect(self)
153
+ @@topic_manager.disconnect(self)
154
+ end
155
+
156
+ def connected?
157
+ @connected
158
+ end
159
+
160
+ def send_message(msg)
161
+ msg.command = "MESSAGE"
162
+ stomp_send_data(msg)
163
+ end
164
+
165
+ def send_receipt(id)
166
+ send_frame("RECEIPT", { 'receipt-id' => id})
167
+ end
168
+
169
+ def send_error(msg)
170
+ send_frame("ERROR",{'message' => 'See below'},msg)
171
+ end
172
+
173
+ def stomp_send_data(frame)
174
+ send_data(frame.to_s)
175
+ puts "Sending frame #{frame.to_s}" if $DEBUG
176
+ end
177
+
178
+ def send_frame(command, headers={}, body='')
179
+ headers['content-length'] = body.size.to_s
180
+ response = StompServer::StompFrame.new(command, headers, body)
181
+ stomp_send_data(response)
182
+ end
183
+ end
184
+
185
+ end
186
+ end
@@ -0,0 +1,140 @@
1
+
2
+ module StompServer
3
+ class Queue
4
+
5
+ def initialize(directory='.stompserver', delete_empty=true)
6
+ @stompid = StompServer::StompId.new
7
+ @delete_empty = delete_empty
8
+ @directory = directory
9
+ Dir.mkdir(@directory) unless File.directory?(@directory)
10
+ if File.exists?("#{@directory}/qinfo")
11
+ qinfo = Hash.new
12
+ File.open("#{@directory}/qinfo", "rb") { |f| qinfo = Marshal.load(f.read)}
13
+ @queues = qinfo[:queues]
14
+ @frames = qinfo[:frames]
15
+ else
16
+ @queues = Hash.new
17
+ @frames = Hash.new
18
+ end
19
+
20
+ @queues.keys.each do |dest|
21
+ puts "Queue #{dest} size=#{@queues[dest][:size]} enqueued=#{@queues[dest][:enqueued]} dequeued=#{@queues[dest][:dequeued]}" if $DEBUG
22
+ end
23
+
24
+ puts "Queue initialized in #{@directory}"
25
+
26
+ # Cleanup dead queues and save the state of the queues every so often. Alternatively we could save the queue state every X number
27
+ # of frames that are put in the queue. Should probably also read it after saving it to confirm integrity.
28
+ # Removed, this badly corrupt the queue when stopping with messages
29
+ #EventMachine::add_periodic_timer 1800, proc {@queues.keys.each {|dest| close_queue(dest)};save_queue_state }
30
+ end
31
+
32
+ def stop
33
+ puts "Shutting down Queue"
34
+
35
+ @queues.keys.each {|dest| close_queue(dest)}
36
+ @queues.keys.each do |dest|
37
+ puts "Queue #{dest} size=#{@queues[dest][:size]} enqueued=#{@queues[dest][:enqueued]} dequeued=#{@queues[dest][:dequeued]}" if $DEBUG
38
+ end
39
+ save_queue_state
40
+ end
41
+
42
+ def save_queue_state
43
+ puts "Saving Queue State" if $DEBUG
44
+ qinfo = {:queues => @queues, :frames => @frames}
45
+ File.open("#{@directory}/qinfo", "wb") { |f| f.write Marshal.dump(qinfo)}
46
+ end
47
+
48
+ def monitor
49
+ stats = Hash.new
50
+ @queues.keys.each do |dest|
51
+ stats[dest] = {'size' => @queues[dest][:size], 'enqueued' => @queues[dest][:enqueued], 'dequeued' => @queues[dest][:dequeued]}
52
+ end
53
+ stats
54
+ end
55
+
56
+ def close_queue(dest)
57
+ if @queues[dest][:size] == 0 and @queues[dest][:frames].size == 0 and @delete_empty
58
+ _close_queue(dest)
59
+ @queues.delete(dest)
60
+ @frames.delete(dest)
61
+ puts "Queue #{dest} removed." if $DEBUG
62
+ end
63
+ end
64
+
65
+ def open_queue(dest)
66
+ @queues[dest] = Hash.new
67
+ @frames[dest] = Hash.new
68
+ @queues[dest][:size] = 0
69
+ @queues[dest][:frames] = Array.new
70
+ @queues[dest][:msgid] = 1
71
+ @queues[dest][:enqueued] = 0
72
+ @queues[dest][:dequeued] = 0
73
+ @queues[dest][:exceptions] = 0
74
+ _open_queue(dest)
75
+ puts "Created queue #{dest}" if $DEBUG
76
+ end
77
+
78
+ def requeue(dest,frame)
79
+ open_queue(dest) unless @queues.has_key?(dest)
80
+ msgid = frame.headers['message-id']
81
+ if frame.headers['max-exceptions'] and @frames[dest][msgid][:exceptions] >= frame.headers['max-exceptions'].to_i
82
+ enqueue("/queue/deadletter",frame)
83
+ return
84
+ end
85
+ writeframe(dest,frame,msgid)
86
+ @queues[dest][:frames].unshift(msgid)
87
+ @frames[dest][msgid][:exceptions] += 1
88
+ @queues[dest][:dequeued] -= 1
89
+ @queues[dest][:exceptions] += 1
90
+ @queues[dest][:size] += 1
91
+ save_queue_state
92
+ return true
93
+ end
94
+
95
+ def enqueue(dest,frame)
96
+ open_queue(dest) unless @queues.has_key?(dest)
97
+ msgid = assign_id(frame, dest)
98
+ writeframe(dest,frame,msgid)
99
+ @queues[dest][:frames].push(msgid)
100
+ @frames[dest][msgid] = Hash.new
101
+ @frames[dest][msgid][:exceptions] =0
102
+ @frames[dest][msgid][:client_id] = frame.headers['client-id'] if frame.headers['client-id']
103
+ @frames[dest][msgid][:expires] = frame.headers['expires'] if frame.headers['expires']
104
+ @queues[dest][:msgid] += 1
105
+ @queues[dest][:enqueued] += 1
106
+ @queues[dest][:size] += 1
107
+ save_queue_state
108
+ return true
109
+ end
110
+
111
+ def dequeue(dest)
112
+ return false unless message_for?(dest)
113
+ msgid = @queues[dest][:frames].shift
114
+ frame = readframe(dest,msgid)
115
+ @queues[dest][:size] -= 1
116
+ @queues[dest][:dequeued] += 1
117
+ @queues[dest].delete(msgid)
118
+ close_queue(dest)
119
+ save_queue_state
120
+ return frame
121
+ end
122
+
123
+ def message_for?(dest)
124
+ return (@queues.has_key?(dest) and (!@queues[dest][:frames].empty?))
125
+ end
126
+
127
+ def writeframe(dest,frame,msgid)
128
+ _writeframe(dest,frame,msgid)
129
+ end
130
+
131
+ def readframe(dest,msgid)
132
+ _readframe(dest,msgid)
133
+ end
134
+
135
+ def assign_id(frame, dest)
136
+ frame.headers['message-id'] = @stompid[@queues[dest][:msgid]]
137
+ end
138
+ end
139
+ end
140
+