stomp 1.0.1 → 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
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
+