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.
- data/lib/stomp.rb +270 -0
- metadata +39 -0
data/lib/stomp.rb
ADDED
@@ -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: []
|