stomp 1.2.4 → 1.2.5

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 (56) hide show
  1. data/CHANGELOG.rdoc +11 -0
  2. data/README.rdoc +38 -26
  3. data/Rakefile +3 -0
  4. data/bin/catstomp +34 -34
  5. data/bin/stompcat +36 -36
  6. data/examples/client11_ex1.rb +64 -55
  7. data/examples/client11_putget1.rb +47 -35
  8. data/examples/conn11_ex1.rb +59 -51
  9. data/examples/conn11_ex2.rb +59 -50
  10. data/examples/conn11_hb1.rb +35 -26
  11. data/examples/consumer.rb +25 -12
  12. data/examples/get11conn_ex1.rb +97 -89
  13. data/examples/get11conn_ex2.rb +55 -47
  14. data/examples/logexamp.rb +66 -52
  15. data/examples/logexamp_ssl.rb +66 -52
  16. data/examples/publisher.rb +21 -10
  17. data/examples/put11conn_ex1.rb +35 -24
  18. data/examples/putget11_rh1.rb +66 -56
  19. data/examples/slogger.rb +65 -52
  20. data/examples/ssl_uc1.rb +24 -13
  21. data/examples/ssl_uc1_ciphers.rb +28 -15
  22. data/examples/ssl_uc2.rb +26 -16
  23. data/examples/ssl_uc2_ciphers.rb +31 -18
  24. data/examples/ssl_uc3.rb +25 -14
  25. data/examples/ssl_uc3_ciphers.rb +31 -18
  26. data/examples/ssl_uc4.rb +26 -15
  27. data/examples/ssl_uc4_ciphers.rb +32 -19
  28. data/examples/ssl_ucx_default_ciphers.rb +25 -12
  29. data/examples/stomp11_common.rb +16 -15
  30. data/examples/topic_consumer.rb +23 -10
  31. data/examples/topic_publisher.rb +22 -8
  32. data/lib/client/utils.rb +116 -0
  33. data/lib/connection/heartbeats.rb +173 -0
  34. data/lib/connection/netio.rb +322 -0
  35. data/lib/connection/utf8.rb +294 -0
  36. data/lib/connection/utils.rb +104 -0
  37. data/lib/stomp/client.rb +127 -179
  38. data/lib/stomp/codec.rb +5 -2
  39. data/lib/stomp/connection.rb +109 -865
  40. data/lib/stomp/constants.rb +52 -33
  41. data/lib/stomp/errors.rb +56 -5
  42. data/lib/stomp/ext/hash.rb +4 -0
  43. data/lib/stomp/message.rb +49 -29
  44. data/lib/stomp/sslparams.rb +83 -71
  45. data/lib/stomp/version.rb +3 -1
  46. data/lib/stomp.rb +18 -9
  47. data/stomp.gemspec +58 -3
  48. data/test/test_client.rb +28 -1
  49. data/test/test_codec.rb +8 -2
  50. data/test/test_connection.rb +29 -0
  51. data/test/test_connection1p.rb +31 -16
  52. data/test/test_helper.rb +20 -3
  53. data/test/test_message.rb +8 -3
  54. data/test/test_ssl.rb +10 -4
  55. data/test/tlogger.rb +16 -15
  56. metadata +59 -4
@@ -0,0 +1,322 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'socket'
4
+ require 'timeout'
5
+ require 'io/wait'
6
+ require 'digest/sha1'
7
+
8
+ module Stomp
9
+
10
+ class Connection
11
+
12
+ private
13
+
14
+ # Really read from the wire.
15
+ def _receive(read_socket)
16
+ @read_semaphore.synchronize do
17
+ line = ''
18
+ if @protocol == Stomp::SPL_10 || (@protocol >= Stomp::SPL_11 && !@hbr)
19
+ line = read_socket.gets # The old way
20
+ else # We are >= 1.1 *AND* receiving heartbeats.
21
+ while true
22
+ line = read_socket.gets # Data from wire
23
+ break unless line == "\n"
24
+ line = ''
25
+ @lr = Time.now.to_f
26
+ end
27
+ end
28
+ return nil if line.nil?
29
+ # If the reading hangs for more than X seconds, abort the parsing process.
30
+ # X defaults to 5. Override allowed in connection hash parameters.
31
+ Timeout::timeout(@parse_timeout, Stomp::Error::PacketParsingTimeout) do
32
+ # Reads the beginning of the message until it runs into a empty line
33
+ message_header = ''
34
+ begin
35
+ message_header += line
36
+ line = read_socket.gets
37
+ raise Stomp::Error::StompServerError if line.nil?
38
+ end until line =~ /^\s?\n$/
39
+
40
+ # Checks if it includes content_length header
41
+ content_length = message_header.match /content-length\s?:\s?(\d+)\s?\n/
42
+ message_body = ''
43
+
44
+ # If content_length is present, read the specified amount of bytes
45
+ if content_length
46
+ message_body = read_socket.read content_length[1].to_i
47
+ raise Stomp::Error::InvalidMessageLength unless parse_char(read_socket.getc) == "\0"
48
+ # Else read the rest of the message until the first \0
49
+ else
50
+ message_body = read_socket.readline("\0")
51
+ message_body.chop!
52
+ end
53
+
54
+ # If the buffer isn't empty, reads trailing new lines.
55
+ #
56
+ # Note: experiments with JRuby seem to show that .ready? never
57
+ # returns true. This means that this code to drain trailing new
58
+ # lines never runs using JRuby.
59
+ #
60
+ # Note 2: the draining of new lines must be done _after_ a message
61
+ # is read. Do _not_ leave them on the wire and attempt to drain them
62
+ # at the start of the next read. Attempting to do that breaks the
63
+ # asynchronous nature of the 'poll' method.
64
+ while read_socket.ready?
65
+ last_char = read_socket.getc
66
+ break unless last_char
67
+ if parse_char(last_char) != "\n"
68
+ read_socket.ungetc(last_char)
69
+ break
70
+ end
71
+ end
72
+ # And so, a JRuby hack. Remove any new lines at the start of the
73
+ # next buffer.
74
+ message_header.gsub!(/^\n?/, "")
75
+
76
+ if @protocol >= Stomp::SPL_11
77
+ @lr = Time.now.to_f if @hbr
78
+ end
79
+ # Adds the excluded \n and \0 and tries to create a new message with it
80
+ msg = Message.new(message_header + "\n" + message_body + "\0", @protocol >= Stomp::SPL_11)
81
+ #
82
+ if @protocol >= Stomp::SPL_11 && msg.command != Stomp::CMD_CONNECTED
83
+ msg.headers = _decodeHeaders(msg.headers)
84
+ end
85
+ msg
86
+ end
87
+ end
88
+ end
89
+
90
+ # transmit logically puts a Message on the wire.
91
+ def transmit(command, headers = {}, body = '')
92
+ # The transmit may fail so we may need to retry.
93
+ while TRUE
94
+ begin
95
+ used_socket = socket
96
+ _transmit(used_socket, command, headers, body)
97
+ return
98
+ rescue Stomp::Error::MaxReconnectAttempts => e
99
+ raise
100
+ rescue
101
+ @failure = $!
102
+ raise unless @reliable
103
+ errstr = "transmit to #{@host} failed: #{$!}\n"
104
+ if @logger && @logger.respond_to?(:on_miscerr)
105
+ @logger.on_miscerr(log_params, errstr)
106
+ else
107
+ $stderr.print errstr
108
+ end
109
+ end
110
+ end
111
+ end
112
+
113
+ # _transmit is the real wire write logic.
114
+ def _transmit(used_socket, command, headers = {}, body = '')
115
+ if @protocol >= Stomp::SPL_11 && command != Stomp::CMD_CONNECT
116
+ headers = _encodeHeaders(headers)
117
+ end
118
+ @transmit_semaphore.synchronize do
119
+ # Handle nil body
120
+ body = '' if body.nil?
121
+ # The content-length should be expressed in bytes.
122
+ # Ruby 1.8: String#length => # of bytes; Ruby 1.9: String#length => # of characters
123
+ # With Unicode strings, # of bytes != # of characters. So, use String#bytesize when available.
124
+ body_length_bytes = body.respond_to?(:bytesize) ? body.bytesize : body.length
125
+
126
+ # ActiveMQ interprets every message as a BinaryMessage
127
+ # if content_length header is included.
128
+ # Using :suppress_content_length => true will suppress this behaviour
129
+ # and ActiveMQ will interpret the message as a TextMessage.
130
+ # For more information refer to http://juretta.com/log/2009/05/24/activemq-jms-stomp/
131
+ # Lets send this header in the message, so it can maintain state when using unreceive
132
+ headers[:'content-length'] = "#{body_length_bytes}" unless headers[:suppress_content_length]
133
+ headers[:'content-type'] = "text/plain; charset=UTF-8" unless headers[:'content-type']
134
+ used_socket.puts command
135
+ headers.each do |k,v|
136
+ if v.is_a?(Array)
137
+ v.each do |e|
138
+ used_socket.puts "#{k}:#{e}"
139
+ end
140
+ else
141
+ used_socket.puts "#{k}:#{v}"
142
+ end
143
+ end
144
+ used_socket.puts
145
+ used_socket.write body
146
+ used_socket.write "\0"
147
+ used_socket.flush if autoflush
148
+
149
+ if @protocol >= Stomp::SPL_11
150
+ @ls = Time.now.to_f if @hbs
151
+ end
152
+
153
+ end
154
+ end
155
+
156
+ # open_tcp_socket opens a TCP socket.
157
+ def open_tcp_socket()
158
+ tcp_socket = nil
159
+
160
+ if @logger && @logger.respond_to?(:on_connecting)
161
+ @logger.on_connecting(log_params)
162
+ end
163
+
164
+ Timeout::timeout(@connect_timeout, Stomp::Error::SocketOpenTimeout) do
165
+ tcp_socket = TCPSocket.open(@host, @port)
166
+ end
167
+
168
+ tcp_socket
169
+ end
170
+
171
+ # open_ssl_socket opens an SSL socket.
172
+ def open_ssl_socket()
173
+ require 'openssl' unless defined?(OpenSSL)
174
+ begin # Any raised SSL exceptions
175
+ ctx = OpenSSL::SSL::SSLContext.new
176
+ ctx.verify_mode = OpenSSL::SSL::VERIFY_NONE # Assume for now
177
+ #
178
+ # Note: if a client uses :ssl => true this results in the gem using
179
+ # the _default_ Ruby ciphers list. This is _known_ to fail in later
180
+ # Ruby releases. The gem provides a default cipher list that may
181
+ # function in these cases. To use this connect with:
182
+ # * :ssl => Stomp::SSLParams.new
183
+ # * :ssl => Stomp::SSLParams.new(..., :ciphers => Stomp::DEFAULT_CIPHERS)
184
+ #
185
+ # If connecting with an SSLParams instance, and the _default_ Ruby
186
+ # ciphers list is required, use:
187
+ # * :ssl => Stomp::SSLParams.new(..., :use_ruby_ciphers => true)
188
+ #
189
+ # If a custom ciphers list is required, connect with:
190
+ # * :ssl => Stomp::SSLParams.new(..., :ciphers => custom_ciphers_list)
191
+ #
192
+ if @ssl != true
193
+ #
194
+ # Here @ssl is:
195
+ # * an instance of Stomp::SSLParams
196
+ # Control would not be here if @ssl == false or @ssl.nil?.
197
+ #
198
+
199
+ # Back reference the SSLContext
200
+ @ssl.ctx = ctx
201
+
202
+ # Server authentication parameters if required
203
+ if @ssl.ts_files
204
+ ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER
205
+ truststores = OpenSSL::X509::Store.new
206
+ fl = @ssl.ts_files.split(",")
207
+ fl.each do |fn|
208
+ # Add next cert file listed
209
+ raise Stomp::Error::SSLNoTruststoreFileError if !File::exists?(fn)
210
+ raise Stomp::Error::SSLUnreadableTruststoreFileError if !File::readable?(fn)
211
+ truststores.add_file(fn)
212
+ end
213
+ ctx.cert_store = truststores
214
+ end
215
+
216
+ # Client authentication parameters.
217
+ # Both cert file and key file must be present or not, it can not be a mix.
218
+ raise Stomp::Error::SSLClientParamsError if @ssl.cert_file.nil? && !@ssl.key_file.nil?
219
+ raise Stomp::Error::SSLClientParamsError if !@ssl.cert_file.nil? && @ssl.key_file.nil?
220
+ if @ssl.cert_file # Any check will do here
221
+ raise Stomp::Error::SSLNoCertFileError if !File::exists?(@ssl.cert_file)
222
+ raise Stomp::Error::SSLUnreadableCertFileError if !File::readable?(@ssl.cert_file)
223
+ ctx.cert = OpenSSL::X509::Certificate.new(File.read(@ssl.cert_file))
224
+ raise Stomp::Error::SSLNoKeyFileError if !File::exists?(@ssl.key_file)
225
+ raise Stomp::Error::SSLUnreadableKeyFileError if !File::readable?(@ssl.key_file)
226
+ ctx.key = OpenSSL::PKey::RSA.new(File.read(@ssl.key_file), @ssl.key_password)
227
+ end
228
+
229
+ # Cipher list
230
+ if !@ssl.use_ruby_ciphers # No Ruby ciphers (the default)
231
+ if @ssl.ciphers # User ciphers list?
232
+ ctx.ciphers = @ssl.ciphers # Accept user supplied ciphers
233
+ else
234
+ ctx.ciphers = Stomp::DEFAULT_CIPHERS # Just use Stomp defaults
235
+ end
236
+ end
237
+ end
238
+
239
+ #
240
+ ssl = nil
241
+ if @logger && @logger.respond_to?(:on_ssl_connecting)
242
+ @logger.on_ssl_connecting(log_params)
243
+ end
244
+
245
+ Timeout::timeout(@connect_timeout, Stomp::Error::SocketOpenTimeout) do
246
+ ssl = OpenSSL::SSL::SSLSocket.new(open_tcp_socket, ctx)
247
+ end
248
+ def ssl.ready?
249
+ ! @rbuffer.empty? || @io.ready?
250
+ end
251
+ ssl.connect
252
+ if @ssl != true
253
+ # Pass back results if possible
254
+ if RUBY_VERSION =~ /1\.8\.[56]/
255
+ @ssl.verify_result = "N/A for Ruby #{RUBY_VERSION}"
256
+ else
257
+ @ssl.verify_result = ssl.verify_result
258
+ end
259
+ @ssl.peer_cert = ssl.peer_cert
260
+ end
261
+ if @logger && @logger.respond_to?(:on_ssl_connected)
262
+ @logger.on_ssl_connected(log_params)
263
+ end
264
+ ssl
265
+ rescue Exception => ex
266
+ if @logger && @logger.respond_to?(:on_ssl_connectfail)
267
+ lp = log_params.clone
268
+ lp[:ssl_exception] = ex
269
+ @logger.on_ssl_connectfail(lp)
270
+ end
271
+ #
272
+ raise # Reraise
273
+ end
274
+ end
275
+
276
+ # close_socket closes the current open socket, and hence the connection.
277
+ def close_socket()
278
+ begin
279
+ # Need to set @closed = true before closing the socket
280
+ # within the @read_semaphore thread
281
+ @closed = true
282
+ @read_semaphore.synchronize do
283
+ @socket.close
284
+ end
285
+ rescue
286
+ #Ignoring if already closed
287
+ end
288
+ @closed
289
+ end
290
+
291
+ # open_socket opens a TCP or SSL soclet as required.
292
+ def open_socket()
293
+ used_socket = @ssl ? open_ssl_socket : open_tcp_socket
294
+ # try to close the old connection if any
295
+ close_socket
296
+
297
+ @closed = false
298
+ # Use keepalive
299
+ used_socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, true)
300
+ used_socket
301
+ end
302
+
303
+ # connect performs a basic STOMP CONNECT operation.
304
+ def connect(used_socket)
305
+ @connect_headers = {} unless @connect_headers # Caller said nil/false
306
+ headers = @connect_headers.clone
307
+ headers[:login] = @login
308
+ headers[:passcode] = @passcode
309
+ _pre_connect
310
+ _transmit(used_socket, "CONNECT", headers)
311
+ @connection_frame = _receive(used_socket)
312
+ _post_connect
313
+ @disconnect_receipt = nil
314
+ @session = @connection_frame.headers["session"] if @connection_frame
315
+ # replay any subscriptions.
316
+ @subscriptions.each { |k,v| _transmit(used_socket, Stomp::CMD_SUBSCRIBE, v) }
317
+ end
318
+
319
+ end # class
320
+
321
+ end # module
322
+
@@ -0,0 +1,294 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ require 'socket'
4
+ require 'timeout'
5
+ require 'io/wait'
6
+ require 'digest/sha1'
7
+
8
+ module Stomp
9
+
10
+ class Connection
11
+
12
+ private
13
+
14
+ # Ref:
15
+ # http://unicode.org/mail-arch/unicode-ml/y2003-m02/att-0467/01-The_Algorithm_to_Valide_an_UTF-8_String
16
+ #
17
+ # *CONSIDER* replacing this with a dependency on the utf8_validator gem.
18
+ # This code has been copied from there.
19
+ #
20
+ def _valid_utf8?(string)
21
+ case RUBY_VERSION
22
+ when /1\.8\.[56]/
23
+ bytes = []
24
+ 0.upto(string.length-1) {|i|
25
+ bytes << string[i]
26
+ }
27
+ else
28
+ bytes = string.bytes
29
+ end
30
+
31
+ #
32
+ valid = true
33
+ index = -1
34
+ nb_hex = nil
35
+ ni_hex = nil
36
+ state = "start"
37
+ next_byte_save = nil
38
+ #
39
+ bytes.each do |next_byte|
40
+ index += 1
41
+ next_byte_save = next_byte
42
+ ni_hex = sprintf "%x", index
43
+ nb_hex = sprintf "%x", next_byte
44
+ # puts "Top: #{next_byte}(0x#{nb_hex}), index: #{index}(0x#{ni_hex})" if DEBUG
45
+ case state
46
+
47
+ # State: 'start'
48
+ # The 'start' state:
49
+ # * handles all occurrences of valid single byte characters i.e., the ASCII character set
50
+ # * provides state transition logic for start bytes of valid characters with 2-4 bytes
51
+ # * signals a validation failure for all other single bytes
52
+ #
53
+ when "start"
54
+ # puts "state: start" if DEBUG
55
+ case next_byte
56
+
57
+ # ASCII
58
+ # * Input = 0x00-0x7F : change state to START
59
+ when (0x00..0x7f)
60
+ # puts "state: start 1" if DEBUG
61
+ state = "start"
62
+
63
+ # Start byte of two byte characters
64
+ # * Input = 0xC2-0xDF: change state to A
65
+ when (0xc2..0xdf)
66
+ # puts "state: start 2" if DEBUG
67
+ state = "a"
68
+
69
+ # Start byte of some three byte characters
70
+ # * Input = 0xE1-0xEC, 0xEE-0xEF: change state to B
71
+ when (0xe1..0xec)
72
+ # puts "state: start 3" if DEBUG
73
+ state = "b"
74
+ when (0xee..0xef)
75
+ # puts "state: start 4" if DEBUG
76
+ state = "b"
77
+
78
+ # Start byte of special three byte characters
79
+ # * Input = 0xE0: change state to C
80
+ when 0xe0
81
+ # puts "state: start 5" if DEBUG
82
+ state = "c"
83
+
84
+ # Start byte of the remaining three byte characters
85
+ # * Input = 0xED: change state to D
86
+ when 0xed
87
+ # puts "state: start 6" if DEBUG
88
+ state = "d"
89
+
90
+ # Start byte of some four byte characters
91
+ # * Input = 0xF1-0xF3:change state to E
92
+ when (0xf1..0xf3)
93
+ # puts "state: start 7" if DEBUG
94
+ state = "e"
95
+
96
+ # Start byte of special four byte characters
97
+ # * Input = 0xF0: change state to F
98
+ when 0xf0
99
+ # puts "state: start 8" if DEBUG
100
+ state = "f"
101
+
102
+ # Start byte of very special four byte characters
103
+ # * Input = 0xF4: change state to G
104
+ when 0xf4
105
+ # puts "state: start 9" if DEBUG
106
+ state = "g"
107
+
108
+ # All other single characters are invalid
109
+ # * Input = Others (0x80-0xBF,0xC0-0xC1, 0xF5-0xFF): ERROR
110
+ else
111
+ valid = false
112
+ break
113
+ end # of the inner case, the 'start' state
114
+
115
+ # The last continuation byte of a 2, 3, or 4 byte character
116
+ # State: 'a'
117
+ # o Input = 0x80-0xBF: change state to START
118
+ # o Others: ERROR
119
+ when "a"
120
+ # puts "state: a" if DEBUG
121
+ if (0x80..0xbf) === next_byte
122
+ state = "start"
123
+ else
124
+ valid = false
125
+ break
126
+ end
127
+
128
+ # The first continuation byte for most 3 byte characters
129
+ # (those with start bytes in: 0xe1-0xec or 0xee-0xef)
130
+ # State: 'b'
131
+ # o Input = 0x80-0xBF: change state to A
132
+ # o Others: ERROR
133
+ when "b"
134
+ # puts "state: b" if DEBUG
135
+ if (0x80..0xbf) === next_byte
136
+ state = "a"
137
+ else
138
+ valid = false
139
+ break
140
+ end
141
+
142
+ # The first continuation byte for some special 3 byte characters
143
+ # (those with start byte 0xe0)
144
+ # State: 'c'
145
+ # o Input = 0xA0-0xBF: change state to A
146
+ # o Others: ERROR
147
+ when "c"
148
+ # puts "state: c" if DEBUG
149
+ if (0xa0..0xbf) === next_byte
150
+ state = "a"
151
+ else
152
+ valid = false
153
+ break
154
+ end
155
+
156
+ # The first continuation byte for the remaining 3 byte characters
157
+ # (those with start byte 0xed)
158
+ # State: 'd'
159
+ # o Input = 0x80-0x9F: change state to A
160
+ # o Others: ERROR
161
+ when "d"
162
+ # puts "state: d" if DEBUG
163
+ if (0x80..0x9f) === next_byte
164
+ state = "a"
165
+ else
166
+ valid = false
167
+ break
168
+ end
169
+
170
+ # The first continuation byte for some 4 byte characters
171
+ # (those with start bytes in: 0xf1-0xf3)
172
+ # State: 'e'
173
+ # o Input = 0x80-0xBF: change state to B
174
+ # o Others: ERROR
175
+ when "e"
176
+ # puts "state: e" if DEBUG
177
+ if (0x80..0xbf) === next_byte
178
+ state = "b"
179
+ else
180
+ valid = false
181
+ break
182
+ end
183
+
184
+ # The first continuation byte for some special 4 byte characters
185
+ # (those with start byte 0xf0)
186
+ # State: 'f'
187
+ # o Input = 0x90-0xBF: change state to B
188
+ # o Others: ERROR
189
+ when "f"
190
+ # puts "state: f" if DEBUG
191
+ if (0x90..0xbf) === next_byte
192
+ state = "b"
193
+ else
194
+ valid = false
195
+ break
196
+ end
197
+
198
+ # The first continuation byte for the remaining 4 byte characters
199
+ # (those with start byte 0xf4)
200
+ # State: 'g'
201
+ # o Input = 0x80-0x8F: change state to B
202
+ # o Others: ERROR
203
+ when "g"
204
+ # puts "state: g" if DEBUG
205
+ if (0x80..0x8f) === next_byte
206
+ state = "b"
207
+ else
208
+ valid = false
209
+ break
210
+ end
211
+
212
+ #
213
+ else
214
+ raise RuntimeError, "state: default"
215
+ end
216
+ end
217
+ #
218
+ # puts "State at end: #{state}" if DEBUG
219
+ # Catch truncation at end of string
220
+ if valid and state != 'start'
221
+ # puts "Resetting valid value" if DEBUG
222
+ valid = false
223
+ end
224
+ #
225
+ valid
226
+ end # of _valid_utf8?
227
+
228
+ # Stomp 1.1+ header check for UTF8 validity. Raises Stomp::Error::UTF8ValidationError if header data is not valid UTF8.
229
+ def _headerCheck(h)
230
+ return if @protocol == Stomp::SPL_10 # Do nothing for this environment
231
+ #
232
+ h.each_pair do |k,v|
233
+ # Keys here are symbolized
234
+ ks = k.to_s
235
+ ks.force_encoding(Stomp::UTF8) if ks.respond_to?(:force_encoding)
236
+ raise Stomp::Error::UTF8ValidationError unless valid_utf8?(ks)
237
+ #
238
+ if v.is_a?(Array)
239
+ v.each do |e|
240
+ e.force_encoding(Stomp::UTF8) if e.respond_to?(:force_encoding)
241
+ raise Stomp::Error::UTF8ValidationError unless valid_utf8?(e)
242
+ end
243
+ else
244
+ vs = v.to_s + "" # Values are usually Strings, but could be TrueClass or Symbol
245
+ # The + "" above forces an 'unfreeze' if necessary
246
+ vs.force_encoding(Stomp::UTF8) if vs.respond_to?(:force_encoding)
247
+ raise Stomp::Error::UTF8ValidationError unless valid_utf8?(vs)
248
+ end
249
+ end
250
+ end
251
+
252
+ # encode returns a Hash of encoded headers per the Stomp 1.1 specification.
253
+ def _encodeHeaders(h)
254
+ eh = {}
255
+ h.each_pair do |k,v|
256
+ # Keys are symbolized
257
+ ks = k.to_s
258
+ if v.is_a?(Array)
259
+ kenc = Stomp::HeaderCodec::encode(ks)
260
+ eh[kenc] = []
261
+ v.each do |e|
262
+ eh[kenc] << Stomp::HeaderCodec::encode(e)
263
+ end
264
+ else
265
+ vs = v.to_s
266
+ eh[Stomp::HeaderCodec::encode(ks)] = Stomp::HeaderCodec::encode(vs)
267
+ end
268
+ end
269
+ eh
270
+ end
271
+
272
+ # decode returns a Hash of decoded headers per the Stomp 1.1 specification.
273
+ def _decodeHeaders(h)
274
+ dh = {}
275
+ h.each_pair do |k,v|
276
+ # Keys here are NOT! symbolized
277
+ if v.is_a?(Array)
278
+ kdec = Stomp::HeaderCodec::decode(k)
279
+ dh[kdec] = []
280
+ v.each do |e|
281
+ dh[kdec] << Stomp::HeaderCodec::decode(e)
282
+ end
283
+ else
284
+ vs = v.to_s
285
+ dh[Stomp::HeaderCodec::decode(k)] = Stomp::HeaderCodec::decode(vs)
286
+ end
287
+ end
288
+ dh
289
+ end
290
+
291
+ end # class
292
+
293
+ end # module
294
+