stomp 1.1.10 → 1.2.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/CHANGELOG.rdoc +17 -0
- data/README.rdoc +11 -3
- data/Rakefile +2 -2
- data/bin/catstomp +3 -3
- data/examples/client11_ex1.rb +78 -0
- data/examples/client11_putget1.rb +57 -0
- data/examples/conn11_ex1.rb +101 -0
- data/examples/conn11_ex2.rb +75 -0
- data/examples/conn11_hb1.rb +46 -0
- data/examples/consumer.rb +2 -0
- data/examples/get11conn_ex1.rb +107 -0
- data/examples/get11conn_ex2.rb +67 -0
- data/examples/logexamp.rb +18 -2
- data/examples/publisher.rb +2 -0
- data/examples/put11conn_ex1.rb +43 -0
- data/examples/putget11_rh1.rb +81 -0
- data/examples/slogger.rb +79 -1
- data/examples/stomp11_common.rb +45 -0
- data/examples/topic_consumer.rb +19 -0
- data/examples/topic_publisher.rb +15 -0
- data/lib/stomp.rb +4 -0
- data/lib/stomp/client.rb +36 -4
- data/lib/stomp/codec.rb +41 -0
- data/lib/stomp/connection.rb +623 -29
- data/lib/stomp/constants.rb +78 -0
- data/lib/stomp/errors.rb +60 -2
- data/lib/stomp/ext/hash.rb +3 -1
- data/lib/stomp/message.rb +32 -3
- data/lib/stomp/version.rb +4 -2
- data/spec/client_shared_examples.rb +2 -0
- data/spec/client_spec.rb +2 -0
- data/spec/connection_spec.rb +30 -9
- data/spec/message_spec.rb +2 -0
- data/spec/spec_helper.rb +2 -0
- data/stomp.gemspec +25 -24
- data/test/test_client.rb +152 -44
- data/test/test_codec.rb +83 -0
- data/test/test_connection.rb +138 -25
- data/test/test_connection1p.rb +251 -0
- data/test/test_helper.rb +48 -0
- data/test/test_message.rb +69 -19
- data/test/tlogger.rb +155 -0
- metadata +52 -69
@@ -0,0 +1,45 @@
|
|
1
|
+
#
|
2
|
+
# Common Stomp 1.1 code.
|
3
|
+
#
|
4
|
+
require "rubygems" if RUBY_VERSION < "1.9"
|
5
|
+
require "stomp"
|
6
|
+
#
|
7
|
+
module Stomp11Common
|
8
|
+
#
|
9
|
+
def login()
|
10
|
+
ENV['STOMP_USER'] || 'guest'
|
11
|
+
end
|
12
|
+
#
|
13
|
+
def passcode()
|
14
|
+
ENV['STOMP_PASSCODE'] || 'guest'
|
15
|
+
end
|
16
|
+
#
|
17
|
+
def host()
|
18
|
+
ENV['STOMP_HOST'] || "localhost" # The connect host name
|
19
|
+
end
|
20
|
+
#
|
21
|
+
def port()
|
22
|
+
(ENV['STOMP_PORT'] || 62613).to_i # !! The author runs Apollo listening here
|
23
|
+
end
|
24
|
+
#
|
25
|
+
def virt_host()
|
26
|
+
ENV['STOMP_VHOST'] || "localhost" # The 1.1 virtual host name
|
27
|
+
end
|
28
|
+
#
|
29
|
+
def get_connection()
|
30
|
+
conn_hdrs = {"accept-version" => "1.1", # 1.1 only
|
31
|
+
"host" => virt_host, # the vhost
|
32
|
+
}
|
33
|
+
conn_hash = { :hosts => [
|
34
|
+
{:login => login, :passcode => passcode, :host => host, :port => port},
|
35
|
+
],
|
36
|
+
:connect_headers => conn_hdrs,
|
37
|
+
}
|
38
|
+
conn = Stomp::Connection.new(conn_hash)
|
39
|
+
end
|
40
|
+
#
|
41
|
+
def nmsgs()
|
42
|
+
(ENV['STOMP_NMSGS'] || 1).to_i # Number of messages
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'stomp'
|
5
|
+
|
6
|
+
client = Stomp::Client.new("failover://(stomp://:@localhost:61613,stomp://:@remotehost:61613)?initialReconnectDelay=5000&randomize=false&useExponentialBackOff=false")
|
7
|
+
|
8
|
+
puts "Subscribing to /topic/ronaldo"
|
9
|
+
|
10
|
+
client.subscribe("/topic/ronaldo") do |msg|
|
11
|
+
puts msg.to_s
|
12
|
+
puts "----------------"
|
13
|
+
end
|
14
|
+
|
15
|
+
loop do
|
16
|
+
sleep(1)
|
17
|
+
puts "."
|
18
|
+
end
|
19
|
+
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'stomp'
|
5
|
+
|
6
|
+
client = Stomp::Client.new("failover://(stomp://:@localhost:61613,stomp://:@remotehost:61613)?initialReconnectDelay=5000&randomize=false&useExponentialBackOff=false")
|
7
|
+
message = "ronaldo #{ARGV[0]}"
|
8
|
+
|
9
|
+
for i in (1..300)
|
10
|
+
puts "Sending message"
|
11
|
+
client.publish("/topic/ronaldo", "#{i}: #{message}")
|
12
|
+
puts "(#{Time.now})Message sent: #{i}"
|
13
|
+
sleep 1
|
14
|
+
end
|
15
|
+
|
data/lib/stomp.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
1
3
|
# Copyright 2005-2006 Brian McCallister
|
2
4
|
# Copyright 2006 LogicBlaze Inc.
|
3
5
|
#
|
@@ -19,6 +21,8 @@ require 'stomp/client'
|
|
19
21
|
require 'stomp/message'
|
20
22
|
require 'stomp/version'
|
21
23
|
require 'stomp/errors'
|
24
|
+
require 'stomp/constants'
|
25
|
+
require 'stomp/codec'
|
22
26
|
|
23
27
|
module Stomp
|
24
28
|
end
|
data/lib/stomp/client.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
1
3
|
require 'thread'
|
2
4
|
require 'digest/sha1'
|
3
5
|
|
@@ -143,7 +145,7 @@ module Stomp
|
|
143
145
|
def subscribe(destination, headers = {})
|
144
146
|
raise "No listener given" unless block_given?
|
145
147
|
# use subscription id to correlate messages to subscription. As described in
|
146
|
-
# the SUBSCRIPTION section of the protocol: http://stomp.
|
148
|
+
# the SUBSCRIPTION section of the protocol: http://stomp.github.com/.
|
147
149
|
# If no subscription id is provided, generate one.
|
148
150
|
set_subscription_id_if_missing(destination, headers)
|
149
151
|
if @listeners[headers[:id]]
|
@@ -180,7 +182,12 @@ module Stomp
|
|
180
182
|
end
|
181
183
|
@connection.ack message.headers['message-id'], headers
|
182
184
|
end
|
183
|
-
|
185
|
+
|
186
|
+
# Stomp 1.1+ NACK
|
187
|
+
def nack(message_id, headers = {})
|
188
|
+
@connection.nack message_id, headers
|
189
|
+
end
|
190
|
+
|
184
191
|
# Unreceive a message, sending it back to its queue or to the DLQ
|
185
192
|
#
|
186
193
|
def unreceive(message, options = {})
|
@@ -238,11 +245,36 @@ module Stomp
|
|
238
245
|
@listener_thread && !!@listener_thread.status
|
239
246
|
end
|
240
247
|
|
248
|
+
# Convenience method
|
249
|
+
def set_logger(logger)
|
250
|
+
@connection.set_logger(logger)
|
251
|
+
end
|
252
|
+
|
253
|
+
# Convenience method
|
254
|
+
def protocol()
|
255
|
+
@connection.protocol
|
256
|
+
end
|
257
|
+
|
258
|
+
# Convenience method
|
259
|
+
def valid_utf8?(s)
|
260
|
+
@connection.valid_utf8?(s)
|
261
|
+
end
|
262
|
+
|
263
|
+
# Convenience method for clients
|
264
|
+
def sha1(data)
|
265
|
+
@connection.sha1(data)
|
266
|
+
end
|
267
|
+
|
268
|
+
# Convenience method for clients
|
269
|
+
def uuid()
|
270
|
+
@connection.uuid()
|
271
|
+
end
|
272
|
+
|
241
273
|
private
|
242
274
|
# Set a subscription id in the headers hash if one does not already exist.
|
243
275
|
# For simplicities sake, all subscriptions have a subscription ID.
|
244
276
|
# setting an id in the SUBSCRIPTION header is described in the stomp protocol docs:
|
245
|
-
# http://stomp.
|
277
|
+
# http://stomp.github.com/
|
246
278
|
def set_subscription_id_if_missing(destination, headers)
|
247
279
|
headers[:id] = headers[:id] ? headers[:id] : headers['id']
|
248
280
|
if headers[:id] == nil
|
@@ -309,7 +341,7 @@ module Stomp
|
|
309
341
|
# For backward compatibility, some messages may already exist with no
|
310
342
|
# subscription id, in which case we can attempt to synthesize one.
|
311
343
|
set_subscription_id_if_missing(message.headers['destination'], message.headers)
|
312
|
-
subscription_id = message.headers[
|
344
|
+
subscription_id = message.headers[:id]
|
313
345
|
end
|
314
346
|
@listeners[subscription_id]
|
315
347
|
end
|
data/lib/stomp/codec.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
module Stomp
|
4
|
+
#
|
5
|
+
# == Purpose
|
6
|
+
#
|
7
|
+
# A general CODEC for STOMP 1.1 header keys and values.
|
8
|
+
#
|
9
|
+
# See:
|
10
|
+
#
|
11
|
+
# * http://stomp.github.com/index.html
|
12
|
+
#
|
13
|
+
# for encode/decode rules.
|
14
|
+
#
|
15
|
+
class HeaderCodec
|
16
|
+
|
17
|
+
# Encode header data per STOMP 1.1 specification
|
18
|
+
def self.encode(in_string = nil)
|
19
|
+
return in_string unless in_string
|
20
|
+
ev = Stomp::ENCODE_VALUES # avoid typing below
|
21
|
+
os = in_string + ""
|
22
|
+
0.step(ev.length-2,2) do |i|
|
23
|
+
os.gsub!(ev[i], ev[i+1])
|
24
|
+
end
|
25
|
+
os
|
26
|
+
end
|
27
|
+
|
28
|
+
# Decode header data per STOMP 1.1 specification
|
29
|
+
def self.decode(in_string = nil)
|
30
|
+
return in_string unless in_string
|
31
|
+
ev = Stomp::DECODE_VALUES # avoid typing below
|
32
|
+
os = in_string + ""
|
33
|
+
0.step(ev.length-2,2) do |i|
|
34
|
+
os.gsub!(ev[i+1], ev[i])
|
35
|
+
end
|
36
|
+
os
|
37
|
+
end
|
38
|
+
|
39
|
+
end # of class HeaderCodec
|
40
|
+
end # of module Stomp
|
41
|
+
|
data/lib/stomp/connection.rb
CHANGED
@@ -1,6 +1,9 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
1
3
|
require 'socket'
|
2
4
|
require 'timeout'
|
3
5
|
require 'io/wait'
|
6
|
+
require 'digest/sha1'
|
4
7
|
|
5
8
|
module Stomp
|
6
9
|
|
@@ -9,6 +12,10 @@ module Stomp
|
|
9
12
|
class Connection
|
10
13
|
attr_reader :connection_frame
|
11
14
|
attr_reader :disconnect_receipt
|
15
|
+
attr_reader :protocol
|
16
|
+
attr_reader :session
|
17
|
+
attr_reader :hb_received # Heartbeat received on time
|
18
|
+
attr_reader :hb_sent # Heartbeat sent successfully
|
12
19
|
#alias :obj_send :send
|
13
20
|
|
14
21
|
def self.default_port(ssl)
|
@@ -33,6 +40,7 @@ module Stomp
|
|
33
40
|
# {:login => "login1", :passcode => "passcode1", :host => "localhost", :port => 61616, :ssl => false},
|
34
41
|
# {:login => "login2", :passcode => "passcode2", :host => "remotehost", :port => 61617, :ssl => false}
|
35
42
|
# ],
|
43
|
+
# :reliable => true,
|
36
44
|
# :initial_reconnect_delay => 0.01,
|
37
45
|
# :max_reconnect_delay => 30.0,
|
38
46
|
# :use_exponential_back_off => true,
|
@@ -40,7 +48,7 @@ module Stomp
|
|
40
48
|
# :max_reconnect_attempts => 0,
|
41
49
|
# :randomize => false,
|
42
50
|
# :backup => false,
|
43
|
-
# :
|
51
|
+
# :connect_timeout => 0,
|
44
52
|
# :connect_headers => {},
|
45
53
|
# :parse_timeout => 5,
|
46
54
|
# :logger => nil,
|
@@ -59,6 +67,10 @@ module Stomp
|
|
59
67
|
#
|
60
68
|
def initialize(login = '', passcode = '', host = 'localhost', port = 61613, reliable = false, reconnect_delay = 5, connect_headers = {})
|
61
69
|
@received_messages = []
|
70
|
+
@protocol = Stomp::SPL_10 # Assumed at first
|
71
|
+
@hb_received = true # Assumed at first
|
72
|
+
@hb_sent = true # Assumed at first
|
73
|
+
@hbs = @hbr = false # Sending/Receiving heartbeats. Assume no for now.
|
62
74
|
|
63
75
|
if login.is_a?(Hash)
|
64
76
|
hashed_initialize(login)
|
@@ -73,6 +85,7 @@ module Stomp
|
|
73
85
|
@ssl = false
|
74
86
|
@parameters = nil
|
75
87
|
@parse_timeout = 5 # To override, use hashed parameters
|
88
|
+
@connect_timeout = 0 # To override, use hashed parameters
|
76
89
|
@logger = nil # To override, use hashed parameters
|
77
90
|
end
|
78
91
|
|
@@ -92,10 +105,11 @@ module Stomp
|
|
92
105
|
def hashed_initialize(params)
|
93
106
|
|
94
107
|
@parameters = refine_params(params)
|
95
|
-
@reliable =
|
108
|
+
@reliable = @parameters[:reliable]
|
96
109
|
@reconnect_delay = @parameters[:initial_reconnect_delay]
|
97
110
|
@connect_headers = @parameters[:connect_headers]
|
98
111
|
@parse_timeout = @parameters[:parse_timeout]
|
112
|
+
@connect_timeout = @parameters[:connect_timeout]
|
99
113
|
@logger = @parameters[:logger]
|
100
114
|
#sets the first host to connect
|
101
115
|
change_host
|
@@ -155,6 +169,7 @@ module Stomp
|
|
155
169
|
|
156
170
|
default_params = {
|
157
171
|
:connect_headers => {},
|
172
|
+
:reliable => true,
|
158
173
|
# Failover parameters
|
159
174
|
:initial_reconnect_delay => 0.01,
|
160
175
|
:max_reconnect_delay => 30.0,
|
@@ -163,7 +178,7 @@ module Stomp
|
|
163
178
|
:max_reconnect_attempts => 0,
|
164
179
|
:randomize => false,
|
165
180
|
:backup => false,
|
166
|
-
:
|
181
|
+
:connect_timeout => 0,
|
167
182
|
# Parse Timeout
|
168
183
|
:parse_timeout => 5
|
169
184
|
}
|
@@ -211,8 +226,11 @@ module Stomp
|
|
211
226
|
|
212
227
|
# Begin a transaction, requires a name for the transaction
|
213
228
|
def begin(name, headers = {})
|
229
|
+
raise Stomp::Error::NoCurrentConnection if closed?
|
230
|
+
headers = headers.symbolize_keys
|
214
231
|
headers[:transaction] = name
|
215
|
-
|
232
|
+
_headerCheck(headers)
|
233
|
+
transmit(Stomp::CMD_BEGIN, headers)
|
216
234
|
end
|
217
235
|
|
218
236
|
# Acknowledge a message, used when a subscription has specified
|
@@ -220,40 +238,83 @@ module Stomp
|
|
220
238
|
#
|
221
239
|
# Accepts a transaction header ( :transaction => 'some_transaction_id' )
|
222
240
|
def ack(message_id, headers = {})
|
223
|
-
|
224
|
-
|
241
|
+
raise Stomp::Error::NoCurrentConnection if closed?
|
242
|
+
raise Stomp::Error::MessageIDRequiredError if message_id.nil? || message_id == ""
|
243
|
+
headers = headers.symbolize_keys
|
244
|
+
headers[:'message-id'] = message_id
|
245
|
+
if @protocol >= Stomp::SPL_11
|
246
|
+
raise Stomp::Error::SubscriptionRequiredError unless headers[:subscription]
|
247
|
+
end
|
248
|
+
_headerCheck(headers)
|
249
|
+
transmit(Stomp::CMD_ACK, headers)
|
250
|
+
end
|
251
|
+
|
252
|
+
# STOMP 1.1+ NACK
|
253
|
+
def nack(message_id, headers = {})
|
254
|
+
raise Stomp::Error::NoCurrentConnection if closed?
|
255
|
+
raise Stomp::Error::UnsupportedProtocolError if @protocol == Stomp::SPL_10
|
256
|
+
raise Stomp::Error::MessageIDRequiredError if message_id.nil? || message_id == ""
|
257
|
+
headers = headers.symbolize_keys
|
258
|
+
headers[:'message-id'] = message_id
|
259
|
+
raise Stomp::Error::SubscriptionRequiredError unless headers[:subscription]
|
260
|
+
_headerCheck(headers)
|
261
|
+
transmit(Stomp::CMD_NACK, headers)
|
225
262
|
end
|
226
263
|
|
227
264
|
# Commit a transaction by name
|
228
265
|
def commit(name, headers = {})
|
266
|
+
raise Stomp::Error::NoCurrentConnection if closed?
|
267
|
+
headers = headers.symbolize_keys
|
229
268
|
headers[:transaction] = name
|
230
|
-
|
269
|
+
_headerCheck(headers)
|
270
|
+
transmit(Stomp::CMD_COMMIT, headers)
|
231
271
|
end
|
232
272
|
|
233
273
|
# Abort a transaction by name
|
234
274
|
def abort(name, headers = {})
|
275
|
+
raise Stomp::Error::NoCurrentConnection if closed?
|
276
|
+
headers = headers.symbolize_keys
|
235
277
|
headers[:transaction] = name
|
236
|
-
|
278
|
+
_headerCheck(headers)
|
279
|
+
transmit(Stomp::CMD_ABORT, headers)
|
237
280
|
end
|
238
281
|
|
239
282
|
# Subscribe to a destination, must specify a name
|
240
283
|
def subscribe(name, headers = {}, subId = nil)
|
284
|
+
raise Stomp::Error::NoCurrentConnection if closed?
|
285
|
+
headers = headers.symbolize_keys
|
241
286
|
headers[:destination] = name
|
242
|
-
|
287
|
+
if @protocol >= Stomp::SPL_11
|
288
|
+
raise Stomp::Error::SubscriptionRequiredError if (headers[:id].nil? && subId.nil?)
|
289
|
+
headers[:id] = subId if headers[:id].nil?
|
290
|
+
end
|
291
|
+
_headerCheck(headers)
|
292
|
+
if @logger && @logger.respond_to?(:on_subscribe)
|
293
|
+
@logger.on_subscribe(log_params, headers)
|
294
|
+
end
|
243
295
|
|
244
296
|
# Store the sub so that we can replay if we reconnect.
|
245
297
|
if @reliable
|
246
298
|
subId = name if subId.nil?
|
299
|
+
raise Stomp::Error::DuplicateSubscription if @subscriptions[subId]
|
247
300
|
@subscriptions[subId] = headers
|
248
301
|
end
|
302
|
+
|
303
|
+
transmit(Stomp::CMD_SUBSCRIBE, headers)
|
249
304
|
end
|
250
305
|
|
251
|
-
# Unsubscribe from a destination, must
|
252
|
-
def unsubscribe(
|
253
|
-
|
254
|
-
|
306
|
+
# Unsubscribe from a destination, which must be specified
|
307
|
+
def unsubscribe(dest, headers = {}, subId = nil)
|
308
|
+
raise Stomp::Error::NoCurrentConnection if closed?
|
309
|
+
headers = headers.symbolize_keys
|
310
|
+
headers[:destination] = dest
|
311
|
+
if @protocol >= Stomp::SPL_11
|
312
|
+
raise Stomp::Error::SubscriptionRequiredError if (headers[:id].nil? && subId.nil?)
|
313
|
+
end
|
314
|
+
_headerCheck(headers)
|
315
|
+
transmit(Stomp::CMD_UNSUBSCRIBE, headers)
|
255
316
|
if @reliable
|
256
|
-
subId =
|
317
|
+
subId = dest if subId.nil?
|
257
318
|
@subscriptions.delete(subId)
|
258
319
|
end
|
259
320
|
end
|
@@ -263,8 +324,14 @@ module Stomp
|
|
263
324
|
# To disable content length header ( :suppress_content_length => true )
|
264
325
|
# Accepts a transaction header ( :transaction => 'some_transaction_id' )
|
265
326
|
def publish(destination, message, headers = {})
|
327
|
+
raise Stomp::Error::NoCurrentConnection if closed?
|
328
|
+
headers = headers.symbolize_keys
|
266
329
|
headers[:destination] = destination
|
267
|
-
|
330
|
+
_headerCheck(headers)
|
331
|
+
if @logger && @logger.respond_to?(:on_publish)
|
332
|
+
@logger.on_publish(log_params, message, headers)
|
333
|
+
end
|
334
|
+
transmit(Stomp::CMD_SEND, headers, message)
|
268
335
|
end
|
269
336
|
|
270
337
|
def obj_send(*args)
|
@@ -282,6 +349,7 @@ module Stomp
|
|
282
349
|
# Accepts a limit number of redeliveries option ( :max_redeliveries => 6 )
|
283
350
|
# Accepts a force client acknowledgement option (:force_client_ack => true)
|
284
351
|
def unreceive(message, options = {})
|
352
|
+
raise Stomp::Error::NoCurrentConnection if closed?
|
285
353
|
options = { :dead_letter_queue => "/queue/DLQ", :max_redeliveries => 6 }.merge options
|
286
354
|
# Lets make sure all keys are symbols
|
287
355
|
message.headers = message.headers.symbolize_keys
|
@@ -318,8 +386,14 @@ module Stomp
|
|
318
386
|
|
319
387
|
# Close this connection
|
320
388
|
def disconnect(headers = {})
|
321
|
-
|
389
|
+
raise Stomp::Error::NoCurrentConnection if closed?
|
322
390
|
headers = headers.symbolize_keys
|
391
|
+
_headerCheck(headers)
|
392
|
+
if @protocol >= Stomp::SPL_11
|
393
|
+
@st.kill if @st # Kill ticker thread if any
|
394
|
+
@rt.kill if @rt # Kill ticker thread if any
|
395
|
+
end
|
396
|
+
transmit(Stomp::CMD_DISCONNECT, headers)
|
323
397
|
@disconnect_receipt = receive if headers[:receipt]
|
324
398
|
if @logger && @logger.respond_to?(:on_disconnect)
|
325
399
|
@logger.on_disconnect(log_params)
|
@@ -330,6 +404,7 @@ module Stomp
|
|
330
404
|
# Return a pending message if one is available, otherwise
|
331
405
|
# return nil
|
332
406
|
def poll
|
407
|
+
raise Stomp::Error::NoCurrentConnection if closed?
|
333
408
|
# No need for a read lock here. The receive method eventually fulfills
|
334
409
|
# that requirement.
|
335
410
|
return nil if @socket.nil? || !@socket.ready?
|
@@ -357,6 +432,7 @@ module Stomp
|
|
357
432
|
end
|
358
433
|
|
359
434
|
def receive
|
435
|
+
raise Stomp::Error::NoCurrentConnection if closed?
|
360
436
|
super_result = __old_receive
|
361
437
|
if super_result.nil? && @reliable && !closed?
|
362
438
|
errstr = "connection.receive returning EOF as nil - resetting connection.\n"
|
@@ -368,18 +444,64 @@ module Stomp
|
|
368
444
|
@socket = nil
|
369
445
|
super_result = __old_receive
|
370
446
|
end
|
447
|
+
#
|
448
|
+
if @logger && @logger.respond_to?(:on_receive)
|
449
|
+
@logger.on_receive(log_params, super_result)
|
450
|
+
end
|
371
451
|
return super_result
|
372
452
|
end
|
373
453
|
|
454
|
+
# Convenience method
|
455
|
+
def set_logger(logger)
|
456
|
+
@logger = logger
|
457
|
+
end
|
458
|
+
|
459
|
+
# Convenience method
|
460
|
+
def valid_utf8?(s)
|
461
|
+
case RUBY_VERSION
|
462
|
+
when /1\.8/
|
463
|
+
rv = _valid_utf8?(s)
|
464
|
+
else
|
465
|
+
rv = s.encoding.name != Stomp::UTF8 ? false : s.valid_encoding?
|
466
|
+
end
|
467
|
+
rv
|
468
|
+
end
|
469
|
+
|
470
|
+
# Convenience method for clients, return a SHA1 digest for arbitrary data
|
471
|
+
def sha1(data)
|
472
|
+
Digest::SHA1.hexdigest(data)
|
473
|
+
end
|
474
|
+
|
475
|
+
# Convenience method for clients, return a type 4 UUID.
|
476
|
+
def uuid()
|
477
|
+
b = []
|
478
|
+
0.upto(15) do |i|
|
479
|
+
b << rand(255)
|
480
|
+
end
|
481
|
+
b[6] = (b[6] & 0x0F) | 0x40
|
482
|
+
b[8] = (b[8] & 0xbf) | 0x80
|
483
|
+
# 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
484
|
+
rs = sprintf("%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x%02x%02x",
|
485
|
+
b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7], b[8], b[9], b[10], b[11], b[12], b[13], b[14], b[15])
|
486
|
+
rs
|
487
|
+
end
|
488
|
+
|
374
489
|
private
|
375
490
|
|
376
491
|
def _receive( read_socket )
|
377
492
|
@read_semaphore.synchronize do
|
378
|
-
# Throw away leading newlines, which are
|
379
|
-
# newlines from the preceding message.
|
493
|
+
# Throw away leading newlines, which are perhaps trailing
|
494
|
+
# newlines from the preceding message, or alterantely a 1.1+ server
|
495
|
+
# heartbeat.
|
380
496
|
begin
|
381
497
|
last_char = read_socket.getc
|
382
498
|
return nil if last_char.nil?
|
499
|
+
if @protocol >= Stomp::SPL_11
|
500
|
+
plc = parse_char(last_char)
|
501
|
+
if plc == "\n" # Server Heartbeat
|
502
|
+
@lr = Time.now.to_f if @hbr
|
503
|
+
end
|
504
|
+
end
|
383
505
|
end until parse_char(last_char) != "\n"
|
384
506
|
read_socket.ungetc(last_char)
|
385
507
|
|
@@ -409,8 +531,17 @@ module Stomp
|
|
409
531
|
message_body << char while (char = parse_char(read_socket.getc)) != "\0"
|
410
532
|
end
|
411
533
|
|
534
|
+
if @protocol >= Stomp::SPL_11
|
535
|
+
@lr = Time.now.to_f if @hbr
|
536
|
+
end
|
537
|
+
|
412
538
|
# Adds the excluded \n and \0 and tries to create a new message with it
|
413
|
-
Message.new(message_header + "\n" + message_body + "\0")
|
539
|
+
msg = Message.new(message_header + "\n" + message_body + "\0", @protocol >= Stomp::SPL_11)
|
540
|
+
#
|
541
|
+
if @protocol >= Stomp::SPL_11 && msg.command != Stomp::CMD_CONNECTED
|
542
|
+
msg.headers = _decodeHeaders(msg.headers)
|
543
|
+
end
|
544
|
+
msg
|
414
545
|
end
|
415
546
|
end
|
416
547
|
end
|
@@ -442,6 +573,9 @@ module Stomp
|
|
442
573
|
end
|
443
574
|
|
444
575
|
def _transmit(used_socket, command, headers = {}, body = '')
|
576
|
+
if @protocol >= Stomp::SPL_11 && command != Stomp::CMD_CONNECT
|
577
|
+
headers = _encodeHeaders(headers)
|
578
|
+
end
|
445
579
|
@transmit_semaphore.synchronize do
|
446
580
|
# Handle nil body
|
447
581
|
body = '' if body.nil?
|
@@ -457,18 +591,33 @@ module Stomp
|
|
457
591
|
# For more information refer to http://juretta.com/log/2009/05/24/activemq-jms-stomp/
|
458
592
|
# Lets send this header in the message, so it can maintain state when using unreceive
|
459
593
|
headers['content-length'] = "#{body_length_bytes}" unless headers[:suppress_content_length]
|
460
|
-
|
594
|
+
headers['content-type'] = "text/plain; charset=UTF-8" unless headers['content-type']
|
461
595
|
used_socket.puts command
|
462
|
-
headers.each
|
463
|
-
|
596
|
+
headers.each do |k,v|
|
597
|
+
if v.is_a?(Array)
|
598
|
+
v.each do |e|
|
599
|
+
used_socket.puts "#{k}:#{e}"
|
600
|
+
end
|
601
|
+
else
|
602
|
+
used_socket.puts "#{k}:#{v}"
|
603
|
+
end
|
604
|
+
end
|
464
605
|
used_socket.puts
|
465
606
|
used_socket.write body
|
466
607
|
used_socket.write "\0"
|
608
|
+
|
609
|
+
if @protocol >= Stomp::SPL_11
|
610
|
+
@ls = Time.now.to_f if @hbs
|
611
|
+
end
|
612
|
+
|
467
613
|
end
|
468
614
|
end
|
469
615
|
|
470
616
|
def open_tcp_socket
|
471
|
-
|
617
|
+
tcp_socket = nil
|
618
|
+
Timeout::timeout(@connect_timeout, Stomp::Error::SocketOpenTimeout) do
|
619
|
+
tcp_socket = TCPSocket.open @host, @port
|
620
|
+
end
|
472
621
|
|
473
622
|
tcp_socket
|
474
623
|
end
|
@@ -489,8 +638,10 @@ module Stomp
|
|
489
638
|
# ctx.cert_store = truststores
|
490
639
|
|
491
640
|
ctx.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
492
|
-
|
493
|
-
|
641
|
+
ssl = nil
|
642
|
+
Timeout::timeout(@connect_timeout, Stomp::Error::SocketOpenTimeout) do
|
643
|
+
ssl = OpenSSL::SSL::SSLSocket.new(open_tcp_socket, ctx)
|
644
|
+
end
|
494
645
|
def ssl.ready?
|
495
646
|
! @rbuffer.empty? || @io.ready?
|
496
647
|
end
|
@@ -524,18 +675,23 @@ module Stomp
|
|
524
675
|
end
|
525
676
|
|
526
677
|
def connect(used_socket)
|
678
|
+
@connect_headers = {} unless @connect_headers # Caller said nil/false
|
527
679
|
headers = @connect_headers.clone
|
528
680
|
headers[:login] = @login
|
529
681
|
headers[:passcode] = @passcode
|
682
|
+
_pre_connect
|
530
683
|
_transmit(used_socket, "CONNECT", headers)
|
531
684
|
@connection_frame = _receive(used_socket)
|
685
|
+
_post_connect
|
532
686
|
@disconnect_receipt = nil
|
687
|
+
@session = @connection_frame.headers["session"] if @connection_frame
|
533
688
|
# replay any subscriptions.
|
534
|
-
@subscriptions.each { |k,v| _transmit(used_socket,
|
689
|
+
@subscriptions.each { |k,v| _transmit(used_socket, Stomp::CMD_SUBSCRIBE, v) }
|
535
690
|
end
|
536
691
|
|
537
692
|
def log_params
|
538
|
-
lparms = @parameters.clone
|
693
|
+
lparms = @parameters.clone if @parameters
|
694
|
+
lparms = {} unless lparms
|
539
695
|
lparms[:cur_host] = @host
|
540
696
|
lparms[:cur_port] = @port
|
541
697
|
lparms[:cur_login] = @login
|
@@ -547,7 +703,445 @@ module Stomp
|
|
547
703
|
#
|
548
704
|
lparms
|
549
705
|
end
|
550
|
-
end
|
551
706
|
|
552
|
-
|
707
|
+
def _pre_connect
|
708
|
+
@connect_headers = @connect_headers.symbolize_keys
|
709
|
+
raise Stomp::Error::ProtocolErrorConnect if (@connect_headers[:"accept-version"] && !@connect_headers[:host])
|
710
|
+
raise Stomp::Error::ProtocolErrorConnect if (!@connect_headers[:"accept-version"] && @connect_headers[:host])
|
711
|
+
return unless (@connect_headers[:"accept-version"] && @connect_headers[:host]) # 1.0
|
712
|
+
# Try 1.1 or greater
|
713
|
+
okvers = []
|
714
|
+
avers = @connect_headers[:"accept-version"].split(",")
|
715
|
+
avers.each do |nver|
|
716
|
+
if Stomp::SUPPORTED.index(nver)
|
717
|
+
okvers << nver
|
718
|
+
end
|
719
|
+
end
|
720
|
+
raise Stomp::Error::UnsupportedProtocolError if okvers == []
|
721
|
+
@connect_headers[:"accept-version"] = okvers.join(",") # This goes to server
|
722
|
+
# Heartbeats - pre connect
|
723
|
+
return unless @connect_headers[:"heart-beat"]
|
724
|
+
_validate_hbheader()
|
725
|
+
end
|
726
|
+
|
727
|
+
def _post_connect
|
728
|
+
return unless (@connect_headers[:"accept-version"] && @connect_headers[:host])
|
729
|
+
return if @connection_frame.command == Stomp::CMD_ERROR
|
730
|
+
cfh = @connection_frame.headers.symbolize_keys
|
731
|
+
@protocol = cfh[:version]
|
732
|
+
# Should not happen, but check anyway
|
733
|
+
raise Stomp::Error::UnsupportedProtocolError unless Stomp::SUPPORTED.index(@protocol)
|
734
|
+
# Heartbeats
|
735
|
+
return unless @connect_headers[:"heart-beat"]
|
736
|
+
_init_heartbeats()
|
737
|
+
end
|
738
|
+
|
739
|
+
def _validate_hbheader()
|
740
|
+
return if @connect_headers[:"heart-beat"] == "0,0" # Caller does not want heartbeats. OK.
|
741
|
+
parts = @connect_headers[:"heart-beat"].split(",")
|
742
|
+
if (parts.size != 2) || (parts[0] != parts[0].to_i.to_s) || (parts[1] != parts[1].to_i.to_s)
|
743
|
+
raise Stomp::Error::InvalidHeartBeatHeaderError
|
744
|
+
end
|
745
|
+
end
|
746
|
+
|
747
|
+
def _init_heartbeats()
|
748
|
+
return if @connect_headers[:"heart-beat"] == "0,0" # Caller does not want heartbeats. OK.
|
749
|
+
#
|
750
|
+
@cx = @cy = @sx = @sy = 0, # Variable names as in spec
|
751
|
+
#
|
752
|
+
@sti = @rti = 0.0 # Send/Receive ticker interval.
|
753
|
+
#
|
754
|
+
@ls = @lr = -1.0 # Last send/receive time (from Time.now.to_f)
|
755
|
+
#
|
756
|
+
@st = @rt = nil # Send/receive ticker thread
|
757
|
+
#
|
758
|
+
cfh = @connection_frame.headers.symbolize_keys
|
759
|
+
return if cfh[:"heart-beat"] == "0,0" # Server does not want heartbeats
|
760
|
+
#
|
761
|
+
parts = @connect_headers[:"heart-beat"].split(",")
|
762
|
+
@cx = parts[0].to_i
|
763
|
+
@cy = parts[1].to_i
|
764
|
+
#
|
765
|
+
parts = cfh[:"heart-beat"].split(",")
|
766
|
+
@sx = parts[0].to_i
|
767
|
+
@sy = parts[1].to_i
|
768
|
+
# Catch odd situations like someone has used => heart-beat:000,00000
|
769
|
+
return if (@cx == 0 && @cy == 0) || (@sx == 0 && @sy == 0)
|
770
|
+
#
|
771
|
+
@hbs = @hbr = true # Sending/Receiving heartbeats. Assume yes at first.
|
772
|
+
# Check for sending
|
773
|
+
@hbs = false if @cx == 0 || @sy == 0
|
774
|
+
# Check for receiving
|
775
|
+
@hbr = false if @sx == 0 || @cy == 0
|
776
|
+
# Should not do heartbeats at all
|
777
|
+
return if (!@hbs && !@hbr)
|
778
|
+
# If sending
|
779
|
+
if @hbs
|
780
|
+
sm = @cx >= @sy ? @cx : @sy # ticker interval, ms
|
781
|
+
@sti = 1000.0 * sm # ticker interval, μs
|
782
|
+
@ls = Time.now.to_f # best guess at start
|
783
|
+
_start_send_ticker
|
784
|
+
end
|
785
|
+
|
786
|
+
# If receiving
|
787
|
+
if @hbr
|
788
|
+
rm = @sx >= @cy ? @sx : @cy # ticker interval, ms
|
789
|
+
@rti = 1000.0 * rm # ticker interval, μs
|
790
|
+
@lr = Time.now.to_f # best guess at start
|
791
|
+
_start_receive_ticker
|
792
|
+
end
|
793
|
+
|
794
|
+
end
|
795
|
+
|
796
|
+
def _start_send_ticker
|
797
|
+
sleeptime = @sti / 1000000.0 # Sleep time secs
|
798
|
+
@st = Thread.new {
|
799
|
+
while true do
|
800
|
+
sleep sleeptime
|
801
|
+
curt = Time.now.to_f
|
802
|
+
if @logger && @logger.respond_to?(:on_hbfire)
|
803
|
+
@logger.on_hbfire(log_params, "send_fire", curt)
|
804
|
+
end
|
805
|
+
delta = curt - @ls
|
806
|
+
if delta > (@sti - (@sti/5.0)) / 1000000.0 # Be tolerant (minus)
|
807
|
+
if @logger && @logger.respond_to?(:on_hbfire)
|
808
|
+
@logger.on_hbfire(log_params, "send_heartbeat", curt)
|
809
|
+
end
|
810
|
+
# Send a heartbeat
|
811
|
+
@transmit_semaphore.synchronize do
|
812
|
+
begin
|
813
|
+
@socket.puts
|
814
|
+
@ls = curt # Update last send
|
815
|
+
@hb_sent = true # Reset if necessary
|
816
|
+
rescue Exception => sendex
|
817
|
+
@hb_sent = false # Set the warning flag
|
818
|
+
if @logger && @logger.respond_to?(:on_hbwrite_fail)
|
819
|
+
@logger.on_hbwrite_fail(log_params, {"ticker_interval" => @sti,
|
820
|
+
"exception" => sendex})
|
821
|
+
end
|
822
|
+
raise # Re-raise. What else could be done here?
|
823
|
+
end
|
824
|
+
end
|
825
|
+
end
|
826
|
+
Thread.pass
|
827
|
+
end
|
828
|
+
}
|
829
|
+
end
|
830
|
+
|
831
|
+
def _start_receive_ticker
|
832
|
+
sleeptime = @rti / 1000000.0 # Sleep time secs
|
833
|
+
@rt = Thread.new {
|
834
|
+
while true do
|
835
|
+
sleep sleeptime
|
836
|
+
curt = Time.now.to_f
|
837
|
+
if @logger && @logger.respond_to?(:on_hbfire)
|
838
|
+
@logger.on_hbfire(log_params, "receive_fire", curt)
|
839
|
+
end
|
840
|
+
delta = curt - @lr
|
841
|
+
if delta > ((@rti + (@rti/5.0)) / 1000000.0) # Be tolerant (plus)
|
842
|
+
if @logger && @logger.respond_to?(:on_hbfire)
|
843
|
+
@logger.on_hbfire(log_params, "receive_heartbeat", curt)
|
844
|
+
end
|
845
|
+
# Client code could be off doing something else (that is, no reading of
|
846
|
+
# the socket has been requested by the caller). Try to handle that case.
|
847
|
+
lock = @read_semaphore.try_lock
|
848
|
+
if lock
|
849
|
+
last_char = @socket.getc
|
850
|
+
plc = parse_char(last_char)
|
851
|
+
if plc == "\n" # Server Heartbeat
|
852
|
+
@lr = Time.now.to_f
|
853
|
+
else
|
854
|
+
@socket.ungetc(last_char)
|
855
|
+
end
|
856
|
+
@read_semaphore.unlock
|
857
|
+
else
|
858
|
+
# Shrug. Have not received one. Just set warning flag.
|
859
|
+
@hb_received = false
|
860
|
+
if @logger && @logger.respond_to?(:on_hbread_fail)
|
861
|
+
@logger.on_hbread_fail(log_params, {"ticker_interval" => @rti})
|
862
|
+
end
|
863
|
+
end
|
864
|
+
else
|
865
|
+
@hb_received = true # Reset if necessary
|
866
|
+
end
|
867
|
+
Thread.pass
|
868
|
+
end
|
869
|
+
}
|
870
|
+
end
|
871
|
+
|
872
|
+
# Ref:
|
873
|
+
# http://unicode.org/mail-arch/unicode-ml/y2003-m02/att-0467/01-The_Algorithm_to_Valide_an_UTF-8_String
|
874
|
+
#
|
875
|
+
def _valid_utf8?(string)
|
876
|
+
case RUBY_VERSION
|
877
|
+
when /1\.8\.[56]/
|
878
|
+
bytes = []
|
879
|
+
0.upto(string.length-1) {|i|
|
880
|
+
bytes << string[i]
|
881
|
+
}
|
882
|
+
else
|
883
|
+
bytes = string.bytes
|
884
|
+
end
|
885
|
+
|
886
|
+
#
|
887
|
+
valid = true
|
888
|
+
index = -1
|
889
|
+
nb_hex = nil
|
890
|
+
ni_hex = nil
|
891
|
+
state = "start"
|
892
|
+
next_byte_save = nil
|
893
|
+
#
|
894
|
+
bytes.each do |next_byte|
|
895
|
+
index += 1
|
896
|
+
next_byte_save = next_byte
|
897
|
+
ni_hex = sprintf "%x", index
|
898
|
+
nb_hex = sprintf "%x", next_byte
|
899
|
+
# puts "Top: #{next_byte}(0x#{nb_hex}), index: #{index}(0x#{ni_hex})" if DEBUG
|
900
|
+
case state
|
901
|
+
|
902
|
+
# State: 'start'
|
903
|
+
# The 'start' state:
|
904
|
+
# * handles all occurrences of valid single byte characters i.e., the ASCII character set
|
905
|
+
# * provides state transition logic for start bytes of valid characters with 2-4 bytes
|
906
|
+
# * signals a validation failure for all other single bytes
|
907
|
+
#
|
908
|
+
when "start"
|
909
|
+
# puts "state: start" if DEBUG
|
910
|
+
case next_byte
|
911
|
+
|
912
|
+
# ASCII
|
913
|
+
# * Input = 0x00-0x7F : change state to START
|
914
|
+
when (0x00..0x7f)
|
915
|
+
# puts "state: start 1" if DEBUG
|
916
|
+
state = "start"
|
917
|
+
|
918
|
+
# Start byte of two byte characters
|
919
|
+
# * Input = 0xC2-0xDF: change state to A
|
920
|
+
when (0xc2..0xdf)
|
921
|
+
# puts "state: start 2" if DEBUG
|
922
|
+
state = "a"
|
923
|
+
|
924
|
+
# Start byte of some three byte characters
|
925
|
+
# * Input = 0xE1-0xEC, 0xEE-0xEF: change state to B
|
926
|
+
when (0xe1..0xec)
|
927
|
+
# puts "state: start 3" if DEBUG
|
928
|
+
state = "b"
|
929
|
+
when (0xee..0xef)
|
930
|
+
# puts "state: start 4" if DEBUG
|
931
|
+
state = "b"
|
932
|
+
|
933
|
+
# Start byte of special three byte characters
|
934
|
+
# * Input = 0xE0: change state to C
|
935
|
+
when 0xe0
|
936
|
+
# puts "state: start 5" if DEBUG
|
937
|
+
state = "c"
|
938
|
+
|
939
|
+
# Start byte of the remaining three byte characters
|
940
|
+
# * Input = 0xED: change state to D
|
941
|
+
when 0xed
|
942
|
+
# puts "state: start 6" if DEBUG
|
943
|
+
state = "d"
|
944
|
+
|
945
|
+
# Start byte of some four byte characters
|
946
|
+
# * Input = 0xF1-0xF3:change state to E
|
947
|
+
when (0xf1..0xf3)
|
948
|
+
# puts "state: start 7" if DEBUG
|
949
|
+
state = "e"
|
950
|
+
|
951
|
+
# Start byte of special four byte characters
|
952
|
+
# * Input = 0xF0: change state to F
|
953
|
+
when 0xf0
|
954
|
+
# puts "state: start 8" if DEBUG
|
955
|
+
state = "f"
|
956
|
+
|
957
|
+
# Start byte of very special four byte characters
|
958
|
+
# * Input = 0xF4: change state to G
|
959
|
+
when 0xf4
|
960
|
+
# puts "state: start 9" if DEBUG
|
961
|
+
state = "g"
|
962
|
+
|
963
|
+
# All other single characters are invalid
|
964
|
+
# * Input = Others (0x80-0xBF,0xC0-0xC1, 0xF5-0xFF): ERROR
|
965
|
+
else
|
966
|
+
valid = false
|
967
|
+
break
|
968
|
+
end # of the inner case, the 'start' state
|
969
|
+
|
970
|
+
# The last continuation byte of a 2, 3, or 4 byte character
|
971
|
+
# State: 'a'
|
972
|
+
# o Input = 0x80-0xBF: change state to START
|
973
|
+
# o Others: ERROR
|
974
|
+
when "a"
|
975
|
+
# puts "state: a" if DEBUG
|
976
|
+
if (0x80..0xbf) === next_byte
|
977
|
+
state = "start"
|
978
|
+
else
|
979
|
+
valid = false
|
980
|
+
break
|
981
|
+
end
|
982
|
+
|
983
|
+
# The first continuation byte for most 3 byte characters
|
984
|
+
# (those with start bytes in: 0xe1-0xec or 0xee-0xef)
|
985
|
+
# State: 'b'
|
986
|
+
# o Input = 0x80-0xBF: change state to A
|
987
|
+
# o Others: ERROR
|
988
|
+
when "b"
|
989
|
+
# puts "state: b" if DEBUG
|
990
|
+
if (0x80..0xbf) === next_byte
|
991
|
+
state = "a"
|
992
|
+
else
|
993
|
+
valid = false
|
994
|
+
break
|
995
|
+
end
|
996
|
+
|
997
|
+
# The first continuation byte for some special 3 byte characters
|
998
|
+
# (those with start byte 0xe0)
|
999
|
+
# State: 'c'
|
1000
|
+
# o Input = 0xA0-0xBF: change state to A
|
1001
|
+
# o Others: ERROR
|
1002
|
+
when "c"
|
1003
|
+
# puts "state: c" if DEBUG
|
1004
|
+
if (0xa0..0xbf) === next_byte
|
1005
|
+
state = "a"
|
1006
|
+
else
|
1007
|
+
valid = false
|
1008
|
+
break
|
1009
|
+
end
|
1010
|
+
|
1011
|
+
# The first continuation byte for the remaining 3 byte characters
|
1012
|
+
# (those with start byte 0xed)
|
1013
|
+
# State: 'd'
|
1014
|
+
# o Input = 0x80-0x9F: change state to A
|
1015
|
+
# o Others: ERROR
|
1016
|
+
when "d"
|
1017
|
+
# puts "state: d" if DEBUG
|
1018
|
+
if (0x80..0x9f) === next_byte
|
1019
|
+
state = "a"
|
1020
|
+
else
|
1021
|
+
valid = false
|
1022
|
+
break
|
1023
|
+
end
|
1024
|
+
|
1025
|
+
# The first continuation byte for some 4 byte characters
|
1026
|
+
# (those with start bytes in: 0xf1-0xf3)
|
1027
|
+
# State: 'e'
|
1028
|
+
# o Input = 0x80-0xBF: change state to B
|
1029
|
+
# o Others: ERROR
|
1030
|
+
when "e"
|
1031
|
+
# puts "state: e" if DEBUG
|
1032
|
+
if (0x80..0xbf) === next_byte
|
1033
|
+
state = "b"
|
1034
|
+
else
|
1035
|
+
valid = false
|
1036
|
+
break
|
1037
|
+
end
|
1038
|
+
|
1039
|
+
# The first continuation byte for some special 4 byte characters
|
1040
|
+
# (those with start byte 0xf0)
|
1041
|
+
# State: 'f'
|
1042
|
+
# o Input = 0x90-0xBF: change state to B
|
1043
|
+
# o Others: ERROR
|
1044
|
+
when "f"
|
1045
|
+
# puts "state: f" if DEBUG
|
1046
|
+
if (0x90..0xbf) === next_byte
|
1047
|
+
state = "b"
|
1048
|
+
else
|
1049
|
+
valid = false
|
1050
|
+
break
|
1051
|
+
end
|
1052
|
+
|
1053
|
+
# The first continuation byte for the remaining 4 byte characters
|
1054
|
+
# (those with start byte 0xf4)
|
1055
|
+
# State: 'g'
|
1056
|
+
# o Input = 0x80-0x8F: change state to B
|
1057
|
+
# o Others: ERROR
|
1058
|
+
when "g"
|
1059
|
+
# puts "state: g" if DEBUG
|
1060
|
+
if (0x80..0x8f) === next_byte
|
1061
|
+
state = "b"
|
1062
|
+
else
|
1063
|
+
valid = false
|
1064
|
+
break
|
1065
|
+
end
|
1066
|
+
|
1067
|
+
#
|
1068
|
+
else
|
1069
|
+
raise RuntimeError, "state: default"
|
1070
|
+
end
|
1071
|
+
end
|
1072
|
+
#
|
1073
|
+
# puts "State at end: #{state}" if DEBUG
|
1074
|
+
# Catch truncation at end of string
|
1075
|
+
if valid and state != 'start'
|
1076
|
+
# puts "Resetting valid value" if DEBUG
|
1077
|
+
valid = false
|
1078
|
+
end
|
1079
|
+
#
|
1080
|
+
valid
|
1081
|
+
end # of _valid_utf8?
|
1082
|
+
|
1083
|
+
def _headerCheck(h)
|
1084
|
+
return if @protocol == Stomp::SPL_10 # Do nothing for this environment
|
1085
|
+
#
|
1086
|
+
h.each_pair do |k,v|
|
1087
|
+
# Keys here are symbolized
|
1088
|
+
ks = k.to_s
|
1089
|
+
ks.force_encoding(Stomp::UTF8) if ks.respond_to?(:force_encoding)
|
1090
|
+
raise Stomp::Error::UTF8ValidationError unless valid_utf8?(ks)
|
1091
|
+
#
|
1092
|
+
if v.is_a?(Array)
|
1093
|
+
v.each do |e|
|
1094
|
+
e.force_encoding(Stomp::UTF8) if e.respond_to?(:force_encoding)
|
1095
|
+
raise Stomp::Error::UTF8ValidationError unless valid_utf8?(e)
|
1096
|
+
end
|
1097
|
+
else
|
1098
|
+
vs = v.to_s # Values are usually Strings, but could be TrueClass or Symbol
|
1099
|
+
vs.force_encoding(Stomp::UTF8) if vs.respond_to?(:force_encoding)
|
1100
|
+
raise Stomp::Error::UTF8ValidationError unless valid_utf8?(vs)
|
1101
|
+
end
|
1102
|
+
end
|
1103
|
+
end
|
1104
|
+
|
1105
|
+
#
|
1106
|
+
def _encodeHeaders(h)
|
1107
|
+
eh = {}
|
1108
|
+
h.each_pair do |k,v|
|
1109
|
+
# Keys are symbolized
|
1110
|
+
ks = k.to_s
|
1111
|
+
if v.is_a?(Array)
|
1112
|
+
kenc = Stomp::HeaderCodec::encode(ks)
|
1113
|
+
eh[kenc] = []
|
1114
|
+
v.each do |e|
|
1115
|
+
eh[kenc] << Stomp::HeaderCodec::encode(e)
|
1116
|
+
end
|
1117
|
+
else
|
1118
|
+
vs = v.to_s
|
1119
|
+
eh[Stomp::HeaderCodec::encode(ks)] = Stomp::HeaderCodec::encode(vs)
|
1120
|
+
end
|
1121
|
+
end
|
1122
|
+
eh
|
1123
|
+
end
|
1124
|
+
|
1125
|
+
#
|
1126
|
+
def _decodeHeaders(h)
|
1127
|
+
dh = {}
|
1128
|
+
h.each_pair do |k,v|
|
1129
|
+
# Keys here are NOT! symbolized
|
1130
|
+
if v.is_a?(Array)
|
1131
|
+
kdec = Stomp::HeaderCodec::decode(k)
|
1132
|
+
dh[kdec] = []
|
1133
|
+
v.each do |e|
|
1134
|
+
dh[kdec] << Stomp::HeaderCodec::decode(e)
|
1135
|
+
end
|
1136
|
+
else
|
1137
|
+
vs = v.to_s
|
1138
|
+
dh[Stomp::HeaderCodec::decode(k)] = Stomp::HeaderCodec::decode(vs)
|
1139
|
+
end
|
1140
|
+
end
|
1141
|
+
dh
|
1142
|
+
end
|
1143
|
+
|
1144
|
+
end # class
|
1145
|
+
|
1146
|
+
end # module
|
553
1147
|
|