stomp 1.0.1 → 1.0.2
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/lib/stomp.rb +169 -47
- metadata +18 -11
data/lib/stomp.rb
CHANGED
@@ -1,3 +1,18 @@
|
|
1
|
+
# Copyright 2005-2006 Brian McCallister
|
2
|
+
# Copyright 2006 LogicBlaze Inc.
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
+
# you may not use this file except in compliance with the License.
|
6
|
+
# You may obtain a copy of the License at
|
7
|
+
#
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
#
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
# See the License for the specific language governing permissions and
|
14
|
+
# limitations under the License.
|
15
|
+
|
1
16
|
require 'io/wait'
|
2
17
|
require 'socket'
|
3
18
|
require 'thread'
|
@@ -8,39 +23,70 @@ module Stomp
|
|
8
23
|
# synchronous receives
|
9
24
|
class Connection
|
10
25
|
|
11
|
-
def Connection.open(login = "", passcode = "", host='localhost', port=61613)
|
12
|
-
Connection.new login, passcode, host, port
|
26
|
+
def Connection.open(login = "", passcode = "", host='localhost', port=61613, reliable=FALSE)
|
27
|
+
Connection.new login, passcode, host, port, reliable
|
13
28
|
end
|
14
29
|
|
15
30
|
# Create a connection, requires a login and passcode.
|
16
31
|
# Can accept a host (default is localhost), and port
|
17
32
|
# (default is 61613) to connect to
|
18
|
-
def initialize(login, passcode, host='localhost', port=61613)
|
33
|
+
def initialize(login, passcode, host='localhost', port=61613, reliable=FALSE)
|
34
|
+
@host = host
|
35
|
+
@port = port
|
36
|
+
@login = login
|
37
|
+
@passcode = passcode
|
19
38
|
@transmit_semaphore = Mutex.new
|
20
39
|
@read_semaphore = Mutex.new
|
21
|
-
|
22
|
-
@
|
23
|
-
|
24
|
-
@
|
25
|
-
@
|
40
|
+
@socket_semaphore = Mutex.new
|
41
|
+
@reliable = reliable
|
42
|
+
@reconnectDelay = 5
|
43
|
+
@closed = FALSE
|
44
|
+
@subscriptions = {}
|
45
|
+
@failure = NIL
|
46
|
+
socket
|
26
47
|
end
|
27
|
-
|
48
|
+
|
49
|
+
def socket
|
50
|
+
# Need to look into why the following synchronize does not work.
|
51
|
+
#@read_semaphore.synchronize do
|
52
|
+
s = @socket;
|
53
|
+
while s == NIL or @failure != NIL
|
54
|
+
@failure = NIL
|
55
|
+
begin
|
56
|
+
s = TCPSocket.open @host, @port
|
57
|
+
_transmit(s, "CONNECT", {:login => @login, :passcode => @passcode})
|
58
|
+
@connect = _receive(s)
|
59
|
+
# replay any subscriptions.
|
60
|
+
@subscriptions.each { |k,v| _transmit(s, "SUBSCRIBE", v) }
|
61
|
+
rescue
|
62
|
+
@failure = $!;
|
63
|
+
s=NIL;
|
64
|
+
raise unless @reliable
|
65
|
+
$stderr.print "connect failed: " + $! +" will retry in #{@reconnectDelay}\n";
|
66
|
+
sleep(@reconnectDelay);
|
67
|
+
end
|
68
|
+
end
|
69
|
+
@socket = s
|
70
|
+
return s;
|
71
|
+
#end
|
72
|
+
end
|
73
|
+
|
28
74
|
# Is this connection open?
|
29
75
|
def open?
|
30
|
-
!@
|
76
|
+
!@closed
|
31
77
|
end
|
32
|
-
|
78
|
+
|
33
79
|
# Is this connection closed?
|
34
80
|
def closed?
|
35
|
-
|
81
|
+
@closed
|
36
82
|
end
|
37
|
-
|
83
|
+
|
38
84
|
# Begin a transaction, requires a name for the transaction
|
39
85
|
def begin name, headers={}
|
40
86
|
headers[:transaction] = name
|
41
87
|
transmit "BEGIN", headers
|
42
88
|
end
|
43
|
-
|
89
|
+
|
44
90
|
# Acknowledge a message, used then a subscription has specified
|
45
91
|
# client acknowledgement ( connection.subscribe "/queue/a", :ack => 'client'g
|
46
92
|
#
|
@@ -49,7 +95,7 @@ module Stomp
|
|
49
95
|
headers['message-id'] = message_id
|
50
96
|
transmit "ACK", headers
|
51
97
|
end
|
52
|
-
|
98
|
+
|
53
99
|
# Commit a transaction by name
|
54
100
|
def commit name, headers={}
|
55
101
|
headers[:transaction] = name
|
@@ -61,19 +107,29 @@ module Stomp
|
|
61
107
|
headers[:transaction] = name
|
62
108
|
transmit "ABORT", headers
|
63
109
|
end
|
64
|
-
|
110
|
+
|
65
111
|
# Subscribe to a destination, must specify a name
|
66
|
-
def subscribe(name, headers = {})
|
112
|
+
def subscribe(name, headers = {}, subId=NIL)
|
67
113
|
headers[:destination] = name
|
68
114
|
transmit "SUBSCRIBE", headers
|
115
|
+
|
116
|
+
# Store the sub so that we can replay if we reconnect.
|
117
|
+
if @reliable
|
118
|
+
subId = name if subId==NIL
|
119
|
+
@subscriptions[subId]=headers
|
120
|
+
end
|
69
121
|
end
|
70
|
-
|
122
|
+
|
71
123
|
# Unsubscribe from a destination, must specify a name
|
72
|
-
def unsubscribe(name, headers = {})
|
124
|
+
def unsubscribe(name, headers = {}, subId=NIL)
|
73
125
|
headers[:destination] = name
|
74
126
|
transmit "UNSUBSCRIBE", headers
|
127
|
+
if @reliable
|
128
|
+
subId = name if subId==NIL
|
129
|
+
@h.delete(subId)
|
130
|
+
end
|
75
131
|
end
|
76
|
-
|
132
|
+
|
77
133
|
# Send message to destination
|
78
134
|
#
|
79
135
|
# Accepts a transaction header ( :transaction => 'some_transaction_id' )
|
@@ -81,60 +137,93 @@ module Stomp
|
|
81
137
|
headers[:destination] = destination
|
82
138
|
transmit "SEND", headers, message
|
83
139
|
end
|
84
|
-
|
140
|
+
|
85
141
|
# Close this connection
|
86
142
|
def disconnect(headers = {})
|
87
143
|
transmit "DISCONNECT", headers
|
88
144
|
end
|
89
|
-
|
145
|
+
|
90
146
|
# Return a pending message if one is available, otherwise
|
91
147
|
# return nil
|
92
148
|
def poll
|
93
149
|
@read_semaphore.synchronize do
|
94
|
-
return nil
|
150
|
+
return nil if @socket==NIL or !@socket.ready?
|
95
151
|
return receive
|
96
152
|
end
|
97
153
|
end
|
98
|
-
|
154
|
+
|
99
155
|
# Receive a frame, block until the frame is received
|
100
156
|
def receive
|
157
|
+
# The recive my fail so we may need to retry.
|
158
|
+
while TRUE
|
159
|
+
begin
|
160
|
+
s = socket
|
161
|
+
return _receive(s)
|
162
|
+
rescue
|
163
|
+
@failure = $!;
|
164
|
+
raise unless @reliable
|
165
|
+
$stderr.print "receive failed: " + $!;
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
private
|
171
|
+
def _receive( s )
|
101
172
|
line = ' '
|
102
173
|
@read_semaphore.synchronize do
|
103
|
-
line =
|
174
|
+
line = s.gets
|
175
|
+
return NIL if line == NIL
|
104
176
|
Message.new do |m|
|
105
177
|
m.command = line.chomp
|
106
178
|
m.headers = {}
|
107
|
-
until (line =
|
179
|
+
until (line = s.gets.chomp) == ''
|
108
180
|
k = (line.strip[0, line.strip.index(':')]).strip
|
109
181
|
v = (line.strip[line.strip.index(':') + 1, line.strip.length]).strip
|
110
182
|
m.headers[k] = v
|
111
183
|
end
|
184
|
+
|
112
185
|
if (m.headers['content-length'])
|
113
|
-
m.body =
|
114
|
-
c =
|
186
|
+
m.body = s.read m.headers['content-length'].to_i
|
187
|
+
c = s.getc
|
115
188
|
raise "Invalid content length received" unless c == 0
|
116
189
|
else
|
117
190
|
m.body = ''
|
118
|
-
until (c =
|
191
|
+
until (c = s.getc) == 0
|
119
192
|
m.body << c.chr
|
120
193
|
end
|
121
194
|
end
|
195
|
+
c = s.getc
|
196
|
+
raise "Invalid frame termination received" unless c == 10
|
122
197
|
end
|
123
198
|
end
|
124
|
-
rescue
|
125
|
-
raise "Closed!"
|
126
199
|
end
|
127
200
|
|
128
201
|
private
|
129
202
|
def transmit(command, headers={}, body='')
|
203
|
+
# The transmit my fail so we may need to retry.
|
204
|
+
while TRUE
|
205
|
+
begin
|
206
|
+
s = socket
|
207
|
+
_transmit(s, command, headers, body)
|
208
|
+
return
|
209
|
+
rescue
|
210
|
+
@failure = $!;
|
211
|
+
raise unless @reliable
|
212
|
+
$stderr.print "transmit failed: " + $!+"\n";
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
private
|
218
|
+
def _transmit(s, command, headers={}, body='')
|
130
219
|
@transmit_semaphore.synchronize do
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
220
|
+
s.puts command
|
221
|
+
headers.each {|k,v| s.puts "#{k}:#{v}" }
|
222
|
+
s.puts "content-length: #{body.length}"
|
223
|
+
s.puts "content-type: text/plain; charset=UTF-8"
|
224
|
+
s.puts
|
225
|
+
s.write body
|
226
|
+
s.write "\0"
|
138
227
|
end
|
139
228
|
end
|
140
229
|
end
|
@@ -142,10 +231,11 @@ module Stomp
|
|
142
231
|
# Container class for frames, misnamed technically
|
143
232
|
class Message
|
144
233
|
attr_accessor :headers, :body, :command
|
234
|
+
|
145
235
|
def initialize
|
146
236
|
yield(self) if block_given?
|
147
237
|
end
|
148
|
-
|
238
|
+
|
149
239
|
def to_s
|
150
240
|
"<Stomp::Message headers=#{headers.inspect} body='#{body}' command='#{command}' >"
|
151
241
|
end
|
@@ -160,29 +250,38 @@ module Stomp
|
|
160
250
|
|
161
251
|
# Accepts a username (default ""), password (default ""),
|
162
252
|
# host (default localhost), and port (default 61613)
|
163
|
-
def initialize user="", pass="", host="localhost", port=61613
|
253
|
+
def initialize user="", pass="", host="localhost", port=61613, reliable=FALSE
|
164
254
|
@id_mutex = Mutex.new
|
165
255
|
@ids = 1
|
166
|
-
@connection = Connection.open user, pass, host, port
|
256
|
+
@connection = Connection.open user, pass, host, port, reliable
|
167
257
|
@listeners = {}
|
168
258
|
@receipt_listeners = {}
|
169
259
|
@running = true
|
260
|
+
@replay_messages_by_txn = Hash.new
|
170
261
|
@listener_thread = Thread.start do
|
171
262
|
while @running
|
172
263
|
message = @connection.receive
|
173
264
|
case
|
265
|
+
when message == NIL:
|
266
|
+
break
|
174
267
|
when message.command == 'MESSAGE':
|
175
|
-
|
176
|
-
|
177
|
-
|
268
|
+
if listener = @listeners[message.headers['destination']]
|
269
|
+
listener.call(message)
|
270
|
+
end
|
178
271
|
when message.command == 'RECEIPT':
|
179
|
-
|
180
|
-
|
181
|
-
|
272
|
+
if listener = @receipt_listeners[message.headers['receipt-id']]
|
273
|
+
listener.call(message)
|
274
|
+
end
|
182
275
|
end
|
183
276
|
end
|
184
277
|
end
|
185
278
|
end
|
279
|
+
|
280
|
+
# Join the listener thread for this client,
|
281
|
+
# generally used to wait for a quit signal
|
282
|
+
def join
|
283
|
+
@listener_thread.join
|
284
|
+
end
|
186
285
|
|
187
286
|
# Accepts a username (default ""), password (default ""),
|
188
287
|
# host (default localhost), and port (default 61613)
|
@@ -198,10 +297,22 @@ module Stomp
|
|
198
297
|
# Abort a transaction by name
|
199
298
|
def abort name, headers={}
|
200
299
|
@connection.abort name, headers
|
300
|
+
|
301
|
+
# lets replay any ack'd messages in this transaction
|
302
|
+
replay_list = @replay_messages_by_txn[name]
|
303
|
+
if replay_list
|
304
|
+
replay_list.each do |message|
|
305
|
+
if listener = @listeners[message.headers['destination']]
|
306
|
+
listener.call(message)
|
307
|
+
end
|
308
|
+
end
|
309
|
+
end
|
201
310
|
end
|
202
311
|
|
203
312
|
# Commit a transaction by name
|
204
313
|
def commit name, headers={}
|
314
|
+
txn_id = headers[:transaction]
|
315
|
+
@replay_messages_by_txn.delete(txn_id)
|
205
316
|
@connection.commit name, headers
|
206
317
|
end
|
207
318
|
|
@@ -226,6 +337,16 @@ module Stomp
|
|
226
337
|
#
|
227
338
|
# Accepts a transaction header ( :transaction => 'some_transaction_id' )
|
228
339
|
def acknowledge message, headers={}
|
340
|
+
txn_id = headers[:transaction]
|
341
|
+
if txn_id
|
342
|
+
# lets keep around messages ack'd in this transaction in case we rollback
|
343
|
+
replay_list = @replay_messages_by_txn[txn_id]
|
344
|
+
if replay_list == nil
|
345
|
+
replay_list = []
|
346
|
+
@replay_messages_by_txn[txn_id] = replay_list
|
347
|
+
end
|
348
|
+
replay_list << message
|
349
|
+
end
|
229
350
|
if block_given?
|
230
351
|
headers['receipt'] = register_receipt_listener lambda {|r| yield r}
|
231
352
|
end
|
@@ -266,5 +387,6 @@ module Stomp
|
|
266
387
|
@receipt_listeners[id] = listener
|
267
388
|
id
|
268
389
|
end
|
390
|
+
|
269
391
|
end
|
270
392
|
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
|
-
rubygems_version: 0.
|
2
|
+
rubygems_version: 0.9.0
|
3
3
|
specification_version: 1
|
4
4
|
name: stomp
|
5
5
|
version: !ruby/object:Gem::Version
|
6
|
-
version: 1.0.
|
7
|
-
date:
|
6
|
+
version: 1.0.2
|
7
|
+
date: 2006-09-22 00:00:00 -07:00
|
8
8
|
summary: Ruby client for the Stomp messaging protocol
|
9
9
|
require_paths:
|
10
|
-
|
10
|
+
- lib
|
11
11
|
email: brianm@apache.org
|
12
12
|
homepage: http://stomp.codehaus.org/
|
13
13
|
rubyforge_project:
|
@@ -18,22 +18,29 @@ bindir: bin
|
|
18
18
|
has_rdoc: false
|
19
19
|
required_ruby_version: !ruby/object:Gem::Version::Requirement
|
20
20
|
requirements:
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
version: 0.0.0
|
21
|
+
- - ">"
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 0.0.0
|
25
24
|
version:
|
26
25
|
platform: ruby
|
27
26
|
signing_key:
|
28
27
|
cert_chain:
|
28
|
+
post_install_message:
|
29
29
|
authors:
|
30
|
-
|
30
|
+
- Brian McCallister
|
31
31
|
files:
|
32
|
-
|
32
|
+
- lib/stomp.rb
|
33
33
|
test_files: []
|
34
|
+
|
34
35
|
rdoc_options: []
|
36
|
+
|
35
37
|
extra_rdoc_files: []
|
38
|
+
|
36
39
|
executables: []
|
40
|
+
|
37
41
|
extensions: []
|
42
|
+
|
38
43
|
requirements: []
|
39
|
-
|
44
|
+
|
45
|
+
dependencies: []
|
46
|
+
|