stomp 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|