stompserver_ng 1.0.6

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.
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
+