stompserver_ng 1.0.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (72) hide show
  1. data/History.txt +159 -0
  2. data/Manifest.txt +71 -0
  3. data/README.txt +172 -0
  4. data/Rakefile +38 -0
  5. data/STATUS +5 -0
  6. data/bin/stompserver_ng +63 -0
  7. data/client/README.txt +1 -0
  8. data/client/both.rb +25 -0
  9. data/client/consume.rb +14 -0
  10. data/client/send.rb +17 -0
  11. data/config/stompserver_ng.conf +11 -0
  12. data/etc/19xcompat/notes.txt +223 -0
  13. data/etc/arutils/README-activerecord.txt +78 -0
  14. data/etc/arutils/cre_mysql.rb +34 -0
  15. data/etc/arutils/cre_postgres.rb +33 -0
  16. data/etc/arutils/cre_sqlite3.rb +28 -0
  17. data/etc/arutils/mysql_boot.sql +12 -0
  18. data/etc/arutils/postgres_boot.sql +14 -0
  19. data/etc/database.mysql.yml +9 -0
  20. data/etc/database.postgres.yml +9 -0
  21. data/etc/passwd.example +3 -0
  22. data/etc/ppqinfo.rb +15 -0
  23. data/etc/runserver.sh +17 -0
  24. data/etc/stompserver_ng +50 -0
  25. data/etc/stompserver_ng.conf +13 -0
  26. data/lib/stomp_server_ng.rb +471 -0
  27. data/lib/stomp_server_ng/protocols/http.rb +128 -0
  28. data/lib/stomp_server_ng/protocols/stomp.rb +407 -0
  29. data/lib/stomp_server_ng/qmonitor.rb +58 -0
  30. data/lib/stomp_server_ng/queue.rb +248 -0
  31. data/lib/stomp_server_ng/queue/activerecord_queue.rb +118 -0
  32. data/lib/stomp_server_ng/queue/ar_message.rb +21 -0
  33. data/lib/stomp_server_ng/queue/ar_reconnect.rb +18 -0
  34. data/lib/stomp_server_ng/queue/dbm_queue.rb +72 -0
  35. data/lib/stomp_server_ng/queue/file_queue.rb +56 -0
  36. data/lib/stomp_server_ng/queue/memory_queue.rb +64 -0
  37. data/lib/stomp_server_ng/queue_manager.rb +302 -0
  38. data/lib/stomp_server_ng/stomp_auth.rb +26 -0
  39. data/lib/stomp_server_ng/stomp_frame.rb +32 -0
  40. data/lib/stomp_server_ng/stomp_frame_recognizer.rb +77 -0
  41. data/lib/stomp_server_ng/stomp_id.rb +32 -0
  42. data/lib/stomp_server_ng/stomp_user.rb +17 -0
  43. data/lib/stomp_server_ng/test_server.rb +21 -0
  44. data/lib/stomp_server_ng/topic_manager.rb +46 -0
  45. data/setup.rb +1585 -0
  46. data/stompserver_ng.gemspec +136 -0
  47. data/test/devserver/props.yaml +5 -0
  48. data/test/devserver/runserver.sh +16 -0
  49. data/test/devserver/stompserver_ng.dbm.conf +12 -0
  50. data/test/devserver/stompserver_ng.file.conf +12 -0
  51. data/test/devserver/stompserver_ng.memory.conf +12 -0
  52. data/test/noserver/mocklogger.rb +12 -0
  53. data/test/noserver/test_queue_manager.rb +134 -0
  54. data/test/noserver/test_stomp_frame.rb +138 -0
  55. data/test/noserver/test_topic_manager.rb +79 -0
  56. data/test/noserver/ts_all_no_server.rb +12 -0
  57. data/test/props.yaml +5 -0
  58. data/test/runalltests.sh +14 -0
  59. data/test/runtest.sh +4 -0
  60. data/test/test_0000_base.rb +107 -0
  61. data/test/test_0001_conn.rb +47 -0
  62. data/test/test_0002_conn_sr.rb +94 -0
  63. data/test/test_0006_client.rb +41 -0
  64. data/test/test_0011_send_recv.rb +74 -0
  65. data/test/test_0015_ack_conn.rb +78 -0
  66. data/test/test_0017_ack_client.rb +78 -0
  67. data/test/test_0019_ack_no_ack.rb +145 -0
  68. data/test/test_0022_ack_noack_conn.rb +123 -0
  69. data/test/test_0030_subscr_id.rb +44 -0
  70. data/test/test_0040_receipt_conn.rb +87 -0
  71. data/test/ts_all_server.rb +10 -0
  72. metadata +196 -0
@@ -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,407 @@
1
+ #
2
+ module StompServer
3
+ module StompServer::Protocols
4
+ #
5
+ VALID_COMMANDS = [
6
+ :abort, # Explicit method supplied
7
+ :ack, # Explicit method supplied
8
+ :begin, # Explicit method supplied
9
+ :commit, # Explicit method supplied
10
+ :connect, # Explicit method supplied
11
+ :disconnect, # Explicit method supplied
12
+ :send, # Explicit method supplied
13
+ :subscribe, # Explicit method supplied
14
+ :unsubscribe # Explicit method supplied
15
+ ]
16
+ #
17
+ # = Stomp Protocol Handler.
18
+ #
19
+ class Stomp < EventMachine::Connection
20
+
21
+ attr_reader :session_id
22
+
23
+ # Protocol handler initialization
24
+ def initialize(*args)
25
+ super(*args)
26
+ #
27
+ @@log = Logger.new(STDOUT)
28
+ @@log.level = StompServer::LogHelper.get_loglevel()
29
+ #
30
+ @@options = (Hash === args.last) ? args.pop : {}
31
+ # Arguments are passed from EventMachine::start_server
32
+ @@auth_required = args[0]
33
+ @@queue_manager = args[1]
34
+ @@topic_manager = args[2]
35
+ @@stompauth = args[3]
36
+ #
37
+ # N.B.: The session ID is an instance variable!
38
+ #
39
+ if @@options[:session_cache] == 0
40
+ lt = Time.now
41
+ @session_id = "ssng_#{lt.to_f}"
42
+ else
43
+ @session_id = StompServer::SessionIDManager.get_cache_id(@@options[:session_cache])
44
+ end
45
+ @@log.debug("#{@session_id} #{self} Session ID assigned")
46
+ #
47
+ @@log.warn("#{@session_id} #{self} Protocol initialization complete")
48
+ end
49
+
50
+ # :stopdoc:
51
+ #
52
+ # <tt>EM::Connection.close_connection()</tt>
53
+ #
54
+ # <tt>EM::Connection.close_connection_after_writing()</tt>
55
+ #
56
+ # <tt>EM::Connection.comm_inactivity_timeout()</tt>
57
+ #
58
+ # <tt>EM::Connection.comm_inactivity_timeout=(value)</tt>
59
+ #
60
+ # <tt>EM::Connection.connection_completed()</tt>
61
+ #
62
+ # <tt>EM::Connection.detach()</tt>
63
+ #
64
+ # <tt>EM::Connection.error?()</tt>
65
+ #
66
+ # <tt>EM::Connection.get_peer_cert()</tt>
67
+ #
68
+ # <tt>EM::Connection.get_peername()</tt>
69
+ #
70
+ # <tt>EM::Connection.get_pid()</tt>
71
+ #
72
+ # <tt>EM::Connection.get_sock_opt(level,option)</tt>
73
+ #
74
+ # <tt>EM::Connection.get_sockname()</tt>
75
+ #
76
+ # <tt>EM::Connection.get_status()</tt>
77
+ #
78
+ # <tt>EM::Connection.notify_readable=(mode)</tt>
79
+ #
80
+ # <tt>EM::Connection.notify_readable?()</tt>
81
+ #
82
+ # <tt>EM::Connection.notify_writable=(mode)</tt>
83
+ #
84
+ # <tt>EM::Connection.notify_writable?()</tt>
85
+ #
86
+ # <tt>EM::Connection.pause()</tt>
87
+ #
88
+ # <tt>EM::Connection.paused?()</tt>
89
+ #
90
+ # <tt>EM::Connection.pending_connect_timeout()</tt>
91
+ #
92
+ # <tt>EM::Connection.pending_connect_timeout=(value)</tt>
93
+ #
94
+
95
+ # :startdoc:
96
+
97
+ # <tt>EM::Connection.post_init()</tt>
98
+ #
99
+ # Protocol handler post initialization.
100
+ def post_init
101
+ @sfr = StompServer::StompFrameRecognizer.new
102
+ @transactions = {}
103
+ @connected = false
104
+ @@log.debug("#{@session_id} protocol post_init complete")
105
+ end
106
+
107
+ # :stopdoc:
108
+
109
+ #
110
+ # <tt>EM::Connection.proxy_incoming_to(conn,bufsize=0)</tt>
111
+ #
112
+ # <tt>EM::Connection.proxy_target_unbound()</tt>
113
+ #
114
+
115
+ # :startdoc:
116
+
117
+ # <tt>EM::Connection.receive_data(data)</tt>
118
+ #
119
+ # Delegate to stomp_receive_data helper.
120
+ #
121
+ def receive_data(data)
122
+ stomp_receive_data(data)
123
+ end
124
+
125
+ # :stopdoc:
126
+
127
+ # <tt>EM::Connection.reconnect(server,port)</tt>
128
+ #
129
+ # <tt>EM::Connection.resume()</tt>
130
+ #
131
+
132
+ # :startdoc:
133
+
134
+ # <tt>EM::Connection.send_data(data)</tt>
135
+ #
136
+ # Just calls super.
137
+ #
138
+ def send_data(data)
139
+ super(data)
140
+ end
141
+
142
+ # :stopdoc:
143
+
144
+ # <tt>EM::Connection.send_datagram(data,recipient_address,recipient_port)</tt>
145
+ #
146
+ # <tt>EM::Connection.send_file_data(filename)</tt>
147
+ #
148
+ # <tt>EM::Connection.set_comm_inactivity_timeout(value)</tt>
149
+ #
150
+ # <tt>EM::Connection.set_pending_connect_timeout(value)</tt>
151
+ #
152
+ # <tt>EM::Connection.ssl_handshake_completed()</tt>
153
+ #
154
+ # <tt>EM::Connection.ssl_verify_peer(cert)</tt>
155
+ #
156
+ # <tt>EM::Connection.start_tls(args={})</tt>
157
+ #
158
+ # <tt>EM::Connection.stop_proxying()</tt>
159
+ #
160
+ # <tt>EM::Connection.stream_file_data(filename, args={})</tt>
161
+ #
162
+
163
+ # :startdoc:
164
+
165
+ # <tt>EM::Connection.unbind()</tt>
166
+ #
167
+ # Unbind the connection.
168
+ #
169
+ def unbind()
170
+ @@log.warn "#{@session_id} Unbind called"
171
+ @connected = false
172
+ @@queue_manager.disconnect(self)
173
+ @@topic_manager.disconnect(self)
174
+ end
175
+
176
+ # :stopdoc:
177
+
178
+ # Stomp Protocol Verbs
179
+
180
+ # :startdoc:
181
+ #
182
+ # Stomp Protocol - ABORT
183
+ #
184
+ def abort(frame, trans=nil)
185
+ raise "#{@session_id} Missing transaction" unless trans
186
+ raise "#{@session_id} transaction does not exist" unless @transactions.has_key?(trans)
187
+ @transactions.delete(trans)
188
+ end
189
+ #
190
+ # Stomp Protocol - ACK
191
+ #
192
+ # Delegated to the queue manager.
193
+ #
194
+ def ack(frame)
195
+ @@queue_manager.ack(self, frame)
196
+ end
197
+ #
198
+ # Stomp Protocol - BEGIN
199
+ #
200
+ def begin(frame, trans=nil)
201
+ raise "#{@session_id} Missing transaction" unless trans
202
+ raise "#{@session_id} transaction exists" if @transactions.has_key?(trans)
203
+ @transactions[trans] = []
204
+ end
205
+ #
206
+ # Stomp Protocol - COMMIT
207
+ #
208
+ def commit(frame, trans=nil)
209
+ raise "#{@session_id} Missing transaction" unless trans
210
+ raise "#{@session_id} transaction does not exist" unless @transactions.has_key?(trans)
211
+ #
212
+ (@transactions[trans]).each do |frame|
213
+ frame.headers.delete('transaction')
214
+ process_frame(frame)
215
+ end
216
+ @transactions.delete(trans)
217
+ end
218
+ #
219
+ # Stomp Protocol - CONNECT
220
+ #
221
+ def connect(frame)
222
+ if @@auth_required
223
+ unless frame.headers['login'] and frame.headers['passcode'] and @@stompauth.authorized[frame.headers['login']] == frame.headers['passcode']
224
+ raise "#{@session_id} {self} Invalid Login"
225
+ end
226
+ end
227
+ @@log.warn "#{@session_id} Connecting"
228
+ response = StompServer::StompFrame.new("CONNECTED", {'session' => @session_id})
229
+ #
230
+ stomp_send_data(response)
231
+ @connected = true
232
+ end
233
+ #
234
+ # Stomp Protocol - DISCONNECT
235
+ #
236
+ def disconnect(frame)
237
+ @@log.warn "#{@session_id} Polite disconnect"
238
+ close_connection_after_writing
239
+ end
240
+ #
241
+ # Stomp Protocol - SEND
242
+ #
243
+ # The stomp SEND verb is by routing through:
244
+ #
245
+ # * receive_data(data)
246
+ # * stomp_receive_data
247
+ # * process_frames
248
+ # * process_frame
249
+ # * use Object#__send__ to call this method
250
+ #
251
+ def send(frame)
252
+ # set message id
253
+ if frame.dest.match(%r|^/queue|)
254
+ @@queue_manager.sendmsg(frame)
255
+ else
256
+ frame.headers['message-id'] = "msg-#stompcma-#{@@topic_manager.next_index}"
257
+ @@topic_manager.sendmsg(frame)
258
+ end
259
+ end
260
+ #
261
+ #
262
+ # Stomp Protocol - SUBSCRIBE
263
+ #
264
+ # Delegated to the queue or topic manager.
265
+ #
266
+ def subscribe(frame)
267
+ use_ack = false
268
+ use_ack = true if frame.headers['ack'] == 'client'
269
+ #
270
+ if frame.headers['id']
271
+ subid = frame.headers['id']
272
+ elsif frame.headers[:id]
273
+ subid = frame.headers[:id]
274
+ else
275
+ subid = nil
276
+ end
277
+ #
278
+ if frame.dest =~ %r|^/queue|
279
+ @@queue_manager.subscribe(frame.dest, self, use_ack, subid)
280
+ else
281
+ @@topic_manager.subscribe(frame.dest, self)
282
+ end
283
+ end
284
+ #
285
+ # Stomp Protocol - UNSUBSCRIBE
286
+ #
287
+ # Delegated to the queue or topic manager.
288
+ #
289
+ def unsubscribe(frame)
290
+ if frame.dest =~ %r|^/queue|
291
+ @@queue_manager.unsubscribe(frame.dest,self)
292
+ else
293
+ @@topic_manager.unsubscribe(frame.dest,self)
294
+ end
295
+ end
296
+
297
+ # :stopdoc:
298
+
299
+ # Helper methods
300
+
301
+ # :startdoc:
302
+ #
303
+ # stomp_receive_data
304
+ #
305
+ # Called from <tt>EM::Connection.receive_data(data)</tt>. This is where
306
+ # we begin processing a set of data fresh off the wire.
307
+ #
308
+ def stomp_receive_data(data)
309
+ begin
310
+ # Limit log message length.
311
+ logdata = data
312
+ logdata = data[0..256] + "...truncated..." if data.length > 256
313
+ @@log.debug "#{@session_id} receive_data: #{logdata.inspect}"
314
+ # Append all data to the recognizer buffer.
315
+ @sfr << data
316
+ # Process any stomp frames in this set of data.
317
+ process_frames
318
+ rescue Exception => e
319
+ @@log.error "#{@session_id} err: #{e} #{e.backtrace.join("\n")}"
320
+ send_error(e.to_s)
321
+ close_connection_after_writing
322
+ end
323
+ end
324
+ #
325
+ # process_frames
326
+ #
327
+ # Handle all stomp frames currently in the recognizer's accumulated
328
+ # array of frames.
329
+ #
330
+ def process_frames
331
+ frame = nil
332
+ process_frame(frame) while frame = @sfr.frames.shift
333
+ end
334
+ #
335
+ # process_frame
336
+ #
337
+ # Process and individual stomp frame.
338
+ #
339
+ def process_frame(frame)
340
+ cmd = frame.command.downcase.to_sym
341
+ raise "#{@session_id} #{self} Unhandled frame: #{cmd}" unless VALID_COMMANDS.include?(cmd)
342
+ raise "#{@session_id} #{self} Not connected" if !@connected && cmd != :connect
343
+ @@log.debug("#{@session_id} process_frame: #{frame.command}")
344
+ # Add session ID to the frame headers
345
+ frame.headers['session'] = @session_id
346
+ # Send receipt first if required
347
+ send_receipt(frame.headers['receipt']) if frame.headers['receipt']
348
+ #
349
+ if trans = frame.headers['transaction']
350
+ # Handle transactional frame if required.
351
+ handle_transaction(frame, trans, cmd)
352
+ else
353
+ # Otherwise, just route the non-transactional frame.
354
+ __send__(cmd, frame) # Object#send alias call
355
+ end
356
+ end
357
+ #
358
+ # handle_transaction
359
+ #
360
+ def handle_transaction(frame, trans, cmd)
361
+ if [:begin, :commit, :abort].include?(cmd)
362
+ __send__(cmd, frame, trans) # Object#send alias call
363
+ else
364
+ raise "#{@session_id} transaction does not exist" unless @transactions.has_key?(trans)
365
+ @transactions[trans] << frame
366
+ end
367
+ end
368
+ #
369
+ # send_error
370
+ #
371
+ # Send a single error frame.
372
+ #
373
+ def send_error(msg)
374
+ send_frame("ERROR",{'message' => 'See below'},msg)
375
+ end
376
+ #
377
+ # send_frame
378
+ #
379
+ # Send an individual stomp frame.
380
+ #
381
+ def send_frame(command, headers={}, body='')
382
+ headers['content-length'] = body.size.to_s
383
+ response = StompServer::StompFrame.new(command, headers, body)
384
+ stomp_send_data(response)
385
+ end
386
+ #
387
+ # send_receipt
388
+ #
389
+ # Send a single receipt frame.
390
+ #
391
+ def send_receipt(id)
392
+ send_frame("RECEIPT", { 'receipt-id' => id})
393
+ end
394
+ #
395
+ # stomp_send_data
396
+ #
397
+ def stomp_send_data(frame)
398
+ @@log.debug "#{@session_id} Sending frame #{frame.to_s}"
399
+ send_data(frame.to_s)
400
+ end
401
+
402
+ #
403
+ end # class Stomp < EventMachine::Connection
404
+ #
405
+ end # module StompServer::Protocols
406
+ end # module StompServer
407
+