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.
Files changed (2) hide show
  1. data/lib/stomp.rb +169 -47
  2. 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
- @socket = TCPSocket.open host, port
23
- transmit "CONNECT", {:login => login, :passcode => passcode}
24
- @started = true
25
- @connect = receive()
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
- !@socket.closed?
76
+ !@closed
31
77
  end
32
-
78
+
33
79
  # Is this connection closed?
34
80
  def closed?
35
- !open?
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 unless @socket.ready?
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 = @socket.gets.chomp while line =~ /\A\s*\Z/
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 = @socket.gets.chomp) == ''
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 = @socket.read m.headers['content-length'].to_i
114
- c = @socket.getc
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 = @socket.getc) == 0
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
- data = String.new
132
- data << command << "\n"
133
- headers.each {|k,v| data << "#{k}:#{v}\n" }
134
- data << "content-length: #{body.length}\n"
135
- data << "content-type: text/plain; charset=UTF-8\n\n"
136
- data << body << "\0"
137
- @socket.write data
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
- if listener = @listeners[message.headers['destination']]
176
- listener.call(message)
177
- end
268
+ if listener = @listeners[message.headers['destination']]
269
+ listener.call(message)
270
+ end
178
271
  when message.command == 'RECEIPT':
179
- if listener = @receipt_listeners[message.headers['receipt-id']]
180
- listener.call(message)
181
- end
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.8.11
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.1
7
- date: 2005-12-17 00:00:00 -08:00
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
- - lib
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
- - !ruby/object:Gem::Version
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
- - Brian McCallister
30
+ - Brian McCallister
31
31
  files:
32
- - lib/stomp.rb
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
- dependencies: []
44
+
45
+ dependencies: []
46
+