stomp 1.0.0

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 +270 -0
  2. metadata +39 -0
@@ -0,0 +1,270 @@
1
+ require 'io/wait'
2
+ require 'socket'
3
+ require 'thread'
4
+
5
+ module Stomp
6
+
7
+ # Low level connection which maps commands and supports
8
+ # synchronous receives
9
+ class Connection
10
+
11
+ def Connection.open(login = "", passcode = "", host='localhost', port=61613)
12
+ Connection.new login, passcode, host, port
13
+ end
14
+
15
+ # Create a connection, requires a login and passcode.
16
+ # Can accept a host (default is localhost), and port
17
+ # (default is 61613) to connect to
18
+ def initialize(login, passcode, host='localhost', port=61613)
19
+ @transmit_semaphore = Mutex.new
20
+ @read_semaphore = Mutex.new
21
+
22
+ @socket = TCPSocket.open host, port
23
+ transmit "CONNECT", {:login => login, :passcode => passcode}
24
+ @started = true
25
+ @connect = receive()
26
+ end
27
+
28
+ # Is this connection open?
29
+ def open?
30
+ !@socket.closed?
31
+ end
32
+
33
+ # Is this connection closed?
34
+ def closed?
35
+ !open?
36
+ end
37
+
38
+ # Begin a transaction, requires a name for the transaction
39
+ def begin name, headers={}
40
+ headers[:transaction] = name
41
+ transmit "BEGIN", headers
42
+ end
43
+
44
+ # Acknowledge a message, used then a subscription has specified
45
+ # client acknowledgement ( connection.subscribe "/queue/a", :ack => 'client'g
46
+ #
47
+ # Accepts a transaction header ( :transaction => 'some_transaction_id' )
48
+ def ack message_id, headers={}
49
+ headers['message-id'] = message_id
50
+ transmit "ACK", headers
51
+ end
52
+
53
+ # Commit a transaction by name
54
+ def commit name, headers={}
55
+ headers[:transaction] = name
56
+ transmit "COMMIT", headers
57
+ end
58
+
59
+ # Abort a transaction by name
60
+ def abort name, headers={}
61
+ headers[:transaction] = name
62
+ transmit "ABORT", headers
63
+ end
64
+
65
+ # Subscribe to a destination, must specify a name
66
+ def subscribe(name, headers = {})
67
+ headers[:destination] = name
68
+ transmit "SUBSCRIBE", headers
69
+ end
70
+
71
+ # Unsubscribe from a destination, must specify a name
72
+ def unsubscribe(name, headers = {})
73
+ headers[:destination] = name
74
+ transmit "UNSUBSCRIBE", headers
75
+ end
76
+
77
+ # Send message to destination
78
+ #
79
+ # Accepts a transaction header ( :transaction => 'some_transaction_id' )
80
+ def send(destination, message, headers={})
81
+ headers[:destination] = destination
82
+ transmit "SEND", headers, message
83
+ end
84
+
85
+ # Close this connection
86
+ def disconnect(headers = {})
87
+ transmit "DISCONNECT", headers
88
+ end
89
+
90
+ # Return a pending message if one is available, otherwise
91
+ # return nil
92
+ def poll
93
+ @read_semaphore.synchronize do
94
+ return nil unless @socket.ready?
95
+ return receive
96
+ end
97
+ end
98
+
99
+ # Receive a frame, block until the frame is received
100
+ def receive
101
+ line = ' '
102
+ @read_semaphore.synchronize do
103
+ line = @socket.gets.chomp while line =~ /\A\s*\Z/
104
+ Message.new do |m|
105
+ m.command = line.chomp
106
+ m.headers = {}
107
+ until (line = @socket.gets.chomp) == ''
108
+ k = (line.strip[0, line.strip.index(':')]).strip
109
+ v = (line.strip[line.strip.index(':') + 1, line.strip.length]).strip
110
+ m.headers[k] = v
111
+ end
112
+ if (m.headers['content-length'])
113
+ m.body = @socket.read m.headers['content-length'].to_i
114
+ c = @socket.getc
115
+ raise "Invalid content length received" unless c == 0
116
+ else
117
+ m.body = ''
118
+ until (c = @socket.getc) == 0
119
+ m.body << c.chr
120
+ end
121
+ end
122
+ end
123
+ end
124
+ rescue
125
+ raise "Closed!"
126
+ end
127
+
128
+ private
129
+ def transmit(command, headers={}, body='')
130
+ @transmit_semaphore.synchronize do
131
+ @socket.puts command
132
+ headers.each {|k,v| @socket.puts "#{k}:#{v}" }
133
+ @socket.puts "content-length: #{body.length}"
134
+ @socket.puts "content-type: text/plain; charset=UTF-8"
135
+ @socket.puts
136
+ @socket.write body
137
+ @socket.write "\0"
138
+ end
139
+ end
140
+ end
141
+
142
+ # Container class for frames, misnamed technically
143
+ class Message
144
+ attr_accessor :headers, :body, :command
145
+ def initialize
146
+ yield(self) if block_given?
147
+ end
148
+
149
+ def to_s
150
+ "<Stomp::Message headers=#{headers.inspect} body='#{body}' command='#{command}' >"
151
+ end
152
+ end
153
+
154
+ # Typical Stomp client class. Uses a listener thread to receive frames
155
+ # from the server, any thread can send.
156
+ #
157
+ # Receives all happen in one thread, so consider not doing much processing
158
+ # in that thread if you have much message volume.
159
+ class Client
160
+
161
+ # Accepts a username (default ""), password (default ""),
162
+ # host (default localhost), and port (default 61613)
163
+ def initialize user="", pass="", host="localhost", port=61613
164
+ @id_mutex = Mutex.new
165
+ @ids = 1
166
+ @connection = Connection.open user, pass, host, port
167
+ @listeners = {}
168
+ @receipt_listeners = {}
169
+ @running = true
170
+ @listener_thread = Thread.start do
171
+ while @running
172
+ message = @connection.receive
173
+ case
174
+ when message.command == 'MESSAGE':
175
+ if listener = @listeners[message.headers['destination']]
176
+ listener.call(message)
177
+ end
178
+ when message.command == 'RECEIPT':
179
+ if listener = @receipt_listeners[message.headers['receipt-id']]
180
+ listener.call(message)
181
+ end
182
+ end
183
+ end
184
+ end
185
+ end
186
+
187
+ # Accepts a username (default ""), password (default ""),
188
+ # host (default localhost), and port (default 61613)
189
+ def self.open user="", pass="", host="localhost", port=61613
190
+ Client.new user, pass, host, port
191
+ end
192
+
193
+ # Begin a transaction by name
194
+ def begin name, headers={}
195
+ @connection.begin name, headers
196
+ end
197
+
198
+ # Abort a transaction by name
199
+ def abort name, headers={}
200
+ @connection.abort name, headers
201
+ end
202
+
203
+ # Commit a transaction by name
204
+ def commit name, headers={}
205
+ @connection.commit name, headers
206
+ end
207
+
208
+ # Subscribe to a destination, must be passed a block
209
+ # which will be used as a callback listener
210
+ #
211
+ # Accepts a transaction header ( :transaction => 'some_transaction_id' )
212
+ def subscribe destination, headers={}
213
+ raise "No listener given" unless block_given?
214
+ @listeners[destination] = lambda {|msg| yield msg}
215
+ @connection.subscribe destination, headers
216
+ end
217
+
218
+ # Unsubecribe from a channel
219
+ def unsubscribe name, headers={}
220
+ @connection.unsubscribe name, headers
221
+ @listeners[name] = nil
222
+ end
223
+
224
+ # Acknowledge a message, used then a subscription has specified
225
+ # client acknowledgement ( connection.subscribe "/queue/a", :ack => 'client'g
226
+ #
227
+ # Accepts a transaction header ( :transaction => 'some_transaction_id' )
228
+ def acknowledge message, headers={}
229
+ if block_given?
230
+ headers['receipt'] = register_receipt_listener lambda {|r| yield r}
231
+ end
232
+ @connection.ack message.headers['message-id'], headers
233
+ end
234
+
235
+ # Send message to destination
236
+ #
237
+ # If a block is given a receipt will be requested and passed to the
238
+ # block on receipt
239
+ #
240
+ # Accepts a transaction header ( :transaction => 'some_transaction_id' )
241
+ def send destination, message, headers = {}
242
+ if block_given?
243
+ headers['receipt'] = register_receipt_listener lambda {|r| yield r}
244
+ end
245
+ @connection.send destination, message, headers
246
+ end
247
+
248
+ # Is this client open?
249
+ def open?
250
+ @connection.open?
251
+ end
252
+
253
+ # Close out resources in use by this client
254
+ def close
255
+ @connection.disconnect
256
+ @running = false
257
+ end
258
+
259
+ private
260
+ def register_receipt_listener listener
261
+ id = -1
262
+ @id_mutex.synchronize do
263
+ id = @ids.to_s
264
+ @ids = @ids.succ
265
+ end
266
+ @receipt_listeners[id] = listener
267
+ id
268
+ end
269
+ end
270
+ end
metadata ADDED
@@ -0,0 +1,39 @@
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.8.11
3
+ specification_version: 1
4
+ name: stomp
5
+ version: !ruby/object:Gem::Version
6
+ version: 1.0.0
7
+ date: 2005-10-23 00:00:00 -07:00
8
+ summary: Ruby client xfor the Stomp messaging protocol
9
+ require_paths:
10
+ - lib
11
+ email: brian@skife.org
12
+ homepage: http://stomp.codehaus.org/
13
+ rubyforge_project:
14
+ description:
15
+ autorequire:
16
+ default_executable:
17
+ bindir: bin
18
+ has_rdoc: false
19
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
20
+ requirements:
21
+ -
22
+ - ">"
23
+ - !ruby/object:Gem::Version
24
+ version: 0.0.0
25
+ version:
26
+ platform: ruby
27
+ signing_key:
28
+ cert_chain:
29
+ authors:
30
+ - Brian McCallister
31
+ files:
32
+ - lib/stomp.rb
33
+ test_files: []
34
+ rdoc_options: []
35
+ extra_rdoc_files: []
36
+ executables: []
37
+ extensions: []
38
+ requirements: []
39
+ dependencies: []