stomp 1.0.0

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 +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: []