stomper 1.0.0 → 2.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.
Files changed (152) hide show
  1. data/.gitignore +5 -0
  2. data/{spec/spec.opts → .rspec} +0 -2
  3. data/Gemfile +4 -0
  4. data/LICENSE +201 -201
  5. data/README.md +130 -0
  6. data/Rakefile +5 -0
  7. data/examples/basic.rb +38 -0
  8. data/examples/events.rb +54 -0
  9. data/features/acking_messages.feature +147 -0
  10. data/features/disconnecting.feature +12 -0
  11. data/features/establish_connection.feature +44 -0
  12. data/features/protocol_version_negotiation.feature +61 -0
  13. data/features/receipts.feature +72 -0
  14. data/features/scopes.feature +32 -0
  15. data/features/secure_connections.feature +38 -0
  16. data/features/send_and_message.feature +28 -0
  17. data/features/steps/acking_messages_steps.rb +39 -0
  18. data/features/steps/disconnecting_steps.rb +8 -0
  19. data/features/steps/establish_connection_steps.rb +74 -0
  20. data/features/steps/frame_transmission_steps.rb +35 -0
  21. data/features/steps/protocol_version_negotiation_steps.rb +15 -0
  22. data/features/steps/receipts_steps.rb +79 -0
  23. data/features/steps/scopes_steps.rb +52 -0
  24. data/features/steps/secure_connections_steps.rb +41 -0
  25. data/features/steps/send_and_message_steps.rb +35 -0
  26. data/features/steps/subscribing_steps.rb +36 -0
  27. data/features/steps/threaded_receiver_steps.rb +8 -0
  28. data/features/steps/transactions_steps.rb +0 -0
  29. data/features/subscribing.feature +151 -0
  30. data/features/support/env.rb +11 -0
  31. data/features/support/header_helpers.rb +12 -0
  32. data/features/support/ssl/README +6 -0
  33. data/features/support/ssl/broker_cert.csr +17 -0
  34. data/features/support/ssl/broker_cert.pem +72 -0
  35. data/features/support/ssl/broker_key.pem +27 -0
  36. data/features/support/ssl/client_cert.csr +17 -0
  37. data/features/support/ssl/client_cert.pem +72 -0
  38. data/features/support/ssl/client_key.pem +27 -0
  39. data/features/support/ssl/demoCA/cacert.pem +17 -0
  40. data/features/support/ssl/demoCA/index.txt +2 -0
  41. data/features/support/ssl/demoCA/index.txt.attr +1 -0
  42. data/features/support/ssl/demoCA/index.txt.attr.old +1 -0
  43. data/features/support/ssl/demoCA/index.txt.old +1 -0
  44. data/features/support/ssl/demoCA/newcerts/01.pem +72 -0
  45. data/features/support/ssl/demoCA/newcerts/02.pem +72 -0
  46. data/features/support/ssl/demoCA/private/cakey.pem +17 -0
  47. data/features/support/ssl/demoCA/serial +1 -0
  48. data/features/support/ssl/demoCA/serial.old +1 -0
  49. data/features/support/test_stomp_server.rb +150 -0
  50. data/features/threaded_receiver.feature +11 -0
  51. data/features/transactions.feature +66 -0
  52. data/lib/stomper.rb +30 -20
  53. data/lib/stomper/connection.rb +442 -102
  54. data/lib/stomper/errors.rb +59 -0
  55. data/lib/stomper/extensions.rb +10 -0
  56. data/lib/stomper/extensions/common.rb +258 -0
  57. data/lib/stomper/extensions/events.rb +213 -0
  58. data/lib/stomper/extensions/heartbeat.rb +101 -0
  59. data/lib/stomper/extensions/scoping.rb +56 -0
  60. data/lib/stomper/frame.rb +54 -0
  61. data/lib/stomper/frame_serializer.rb +217 -0
  62. data/lib/stomper/headers.rb +15 -0
  63. data/lib/stomper/receipt_manager.rb +36 -0
  64. data/lib/stomper/receivers.rb +7 -0
  65. data/lib/stomper/receivers/threaded.rb +71 -0
  66. data/lib/stomper/scopes.rb +9 -0
  67. data/lib/stomper/scopes/header_scope.rb +49 -0
  68. data/lib/stomper/scopes/receipt_scope.rb +44 -0
  69. data/lib/stomper/scopes/transaction_scope.rb +109 -0
  70. data/lib/stomper/sockets.rb +66 -28
  71. data/lib/stomper/subscription_manager.rb +79 -0
  72. data/lib/stomper/support.rb +68 -0
  73. data/lib/stomper/support/1.8/frame_serializer.rb +53 -0
  74. data/lib/stomper/support/1.8/headers.rb +183 -0
  75. data/lib/stomper/support/1.9/frame_serializer.rb +64 -0
  76. data/lib/stomper/support/1.9/headers.rb +172 -0
  77. data/lib/stomper/support/ruby.rb +13 -0
  78. data/lib/stomper/uris.rb +49 -0
  79. data/lib/stomper/version.rb +7 -0
  80. data/spec/spec_helper.rb +13 -9
  81. data/spec/stomper/connection_spec.rb +712 -0
  82. data/spec/stomper/extensions/common_spec.rb +187 -0
  83. data/spec/stomper/extensions/events_spec.rb +78 -0
  84. data/spec/stomper/extensions/heartbeat_spec.rb +103 -0
  85. data/spec/stomper/extensions/scoping_spec.rb +21 -0
  86. data/spec/stomper/frame_serializer_1.8_spec.rb +318 -0
  87. data/spec/stomper/frame_serializer_spec.rb +316 -0
  88. data/spec/stomper/frame_spec.rb +36 -0
  89. data/spec/stomper/headers_spec.rb +224 -0
  90. data/spec/stomper/receipt_manager_spec.rb +91 -0
  91. data/spec/stomper/receivers/threaded_spec.rb +116 -0
  92. data/spec/stomper/scopes/header_scope_spec.rb +42 -0
  93. data/spec/stomper/scopes/receipt_scope_spec.rb +51 -0
  94. data/spec/stomper/scopes/transaction_scope_spec.rb +183 -0
  95. data/spec/stomper/sockets_spec.rb +113 -0
  96. data/spec/stomper/subscription_manager_spec.rb +107 -0
  97. data/spec/stomper/support_spec.rb +69 -0
  98. data/spec/stomper/uris_spec.rb +54 -0
  99. data/spec/stomper_spec.rb +9 -0
  100. data/spec/support/custom_argument_matchers.rb +57 -0
  101. data/spec/support/existential_frame_matchers.rb +19 -0
  102. data/spec/support/frame_header_matchers.rb +10 -0
  103. data/stomper.gemspec +30 -0
  104. metadata +272 -97
  105. data/AUTHORS +0 -21
  106. data/CHANGELOG +0 -20
  107. data/README.rdoc +0 -120
  108. data/lib/stomper/client.rb +0 -34
  109. data/lib/stomper/frame_reader.rb +0 -73
  110. data/lib/stomper/frame_writer.rb +0 -21
  111. data/lib/stomper/frames.rb +0 -39
  112. data/lib/stomper/frames/abort.rb +0 -10
  113. data/lib/stomper/frames/ack.rb +0 -25
  114. data/lib/stomper/frames/begin.rb +0 -11
  115. data/lib/stomper/frames/client_frame.rb +0 -89
  116. data/lib/stomper/frames/commit.rb +0 -10
  117. data/lib/stomper/frames/connect.rb +0 -10
  118. data/lib/stomper/frames/connected.rb +0 -30
  119. data/lib/stomper/frames/disconnect.rb +0 -10
  120. data/lib/stomper/frames/error.rb +0 -21
  121. data/lib/stomper/frames/message.rb +0 -48
  122. data/lib/stomper/frames/receipt.rb +0 -19
  123. data/lib/stomper/frames/send.rb +0 -10
  124. data/lib/stomper/frames/server_frame.rb +0 -38
  125. data/lib/stomper/frames/subscribe.rb +0 -42
  126. data/lib/stomper/frames/unsubscribe.rb +0 -19
  127. data/lib/stomper/open_uri_interface.rb +0 -41
  128. data/lib/stomper/receipt_handlers.rb +0 -23
  129. data/lib/stomper/receiptor.rb +0 -38
  130. data/lib/stomper/subscriber.rb +0 -76
  131. data/lib/stomper/subscription.rb +0 -128
  132. data/lib/stomper/subscriptions.rb +0 -95
  133. data/lib/stomper/threaded_receiver.rb +0 -59
  134. data/lib/stomper/transaction.rb +0 -185
  135. data/lib/stomper/transactor.rb +0 -50
  136. data/lib/stomper/uri.rb +0 -55
  137. data/spec/client_spec.rb +0 -29
  138. data/spec/connection_spec.rb +0 -22
  139. data/spec/frame_reader_spec.rb +0 -37
  140. data/spec/frame_writer_spec.rb +0 -27
  141. data/spec/frames/client_frame_spec.rb +0 -66
  142. data/spec/frames/indirect_frame_spec.rb +0 -45
  143. data/spec/frames/server_frame_spec.rb +0 -85
  144. data/spec/open_uri_interface_spec.rb +0 -132
  145. data/spec/receiptor_spec.rb +0 -35
  146. data/spec/shared_connection_examples.rb +0 -79
  147. data/spec/subscriber_spec.rb +0 -77
  148. data/spec/subscription_spec.rb +0 -157
  149. data/spec/subscriptions_spec.rb +0 -145
  150. data/spec/threaded_receiver_spec.rb +0 -33
  151. data/spec/transaction_spec.rb +0 -139
  152. data/spec/transactor_spec.rb +0 -46
@@ -0,0 +1,68 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ # Module that stores methods that add supporiting functionality to Stomper and
4
+ # support for Ruby 1.9 and 1.8.7.
5
+ module Stomper::Support
6
+ # Duplicates an existing hash while transforming its keys to symbols.
7
+ # The keys must implement the +to_sym+ method, otherwise an exception will
8
+ # be raised. This method is used internally to convert hashes keyed with
9
+ # Strings.
10
+ #
11
+ # @param [{Object => Object}] hsh The hash to convert. It's keys must respond to +to_sym+.
12
+ # @return [{Symbol => Object}]
13
+ # @example
14
+ # hash = { '10' => nil, 'key2' => [3, 5, 8, 13, 21], :other => :value }
15
+ # Stomper::Helpers::Hash.keys_to_sym(hash) #=> { :'10' => nil, :key2 => [3, 5, 8, 13, 21], :other => :value }
16
+ # hash #=> { '10' => nil, 'key2' => [3, 5, 8, 13, 21], :other => :value }
17
+ def self.keys_to_sym(hsh)
18
+ hsh.inject({}) do |new_hash, (k,v)|
19
+ new_hash[k.to_sym] = v
20
+ new_hash
21
+ end
22
+ end
23
+
24
+ # Replaces the keys of a hash with symbolized versions.
25
+ # The keys must implement the +to_sym+ method, otherwise an exception will
26
+ # be raised. This method is used internally to convert hashes keyed with
27
+ # Strings.
28
+ #
29
+ # @param [{Object => Object}] hsh The hash to convert. It's keys must respond to +to_sym+.
30
+ # @return [{Symbol => Object}]
31
+ # @example
32
+ # hash = { '10' => nil, 'key2' => [3, 5, 8, 13, 21], :other => :value }
33
+ # Stomper::Helpers::Hash.keys_to_sym!(hash) #=> { :'10' => nil, :key2 => [3, 5, 8, 13, 21], :other => :value }
34
+ # hash #=> { :'10' => nil, :key2 => [3, 5, 8, 13, 21], :other => :value }
35
+ def self.keys_to_sym!(hsh)
36
+ hsh.replace(keys_to_sym(hsh))
37
+ end
38
+
39
+ # Generates the next serial number in a thread-safe manner. This method
40
+ # merely initializes an instance variable to 0 if it has not been set,
41
+ # then increments this value and returns its string representation.
42
+ def self.next_serial(prefix=nil)
43
+ Thread.exclusive do
44
+ @next_serial_sequence ||= 0
45
+ @next_serial_sequence += 1
46
+ @next_serial_sequence.to_s
47
+ end
48
+ end
49
+
50
+ # Converts a string to the Ruby constant it names. If the +klass+ parameter
51
+ # is a kind of +Module+, this method will return +klass+ directly.
52
+ # @param [String,Module] klass
53
+ # @return [Module]
54
+ # @example
55
+ # Stomper::Support.constantize('Stomper::Frame') #=> Stomper::Frame
56
+ # Stomper::Support.constantize('This::Constant::DoesNotExist) #=> raises NameError
57
+ # Stomper::Support.constantize(Symbol) #=> Symbol
58
+ def self.constantize(klass)
59
+ return klass if klass.is_a?(Module)
60
+ klass.to_s.split('::').inject(Object) do |const, named|
61
+ next const if named.empty?
62
+ const.const_defined?(named) ? const.const_get(named) :
63
+ const.const_missing(named)
64
+ end
65
+ end
66
+ end
67
+
68
+ require 'stomper/support/ruby'
@@ -0,0 +1,53 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ # Implementation of {Stomper::FrameSerializer} methods for Ruby 1.8.7
4
+ module Stomper::Support::Ruby1_8::FrameSerializer
5
+ # Return the body with the specified encoding applied. Ruby 1.8 does not
6
+ # have a native awareness of string encodings. As such, this method does
7
+ # not change the body in any way.
8
+ # @param [String] body body of message to encode
9
+ # @param [String] ct content-type header of frame
10
+ # @return [String]
11
+ def encode_body(body, ct_header)
12
+ body
13
+ end
14
+
15
+ # Reads a single byte from the body portion of a frame. Returns +nil+ if
16
+ # the character read is a {::Stomper::FrameSerializer::FRAME_TERMINATOR frame terminator}.
17
+ # @return [String,nil] the byte read from the stream.
18
+ def get_body_byte
19
+ c = @io.getc.chr
20
+ c == ::Stomper::FrameSerializer::FRAME_TERMINATOR ? nil : c
21
+ end
22
+
23
+ # Determines the content-type of a frame being sent to a broker. This
24
+ # version of the method is used by Ruby 1.8.7, which lacks native string
25
+ # encoding support. If the content-type matches 'text/*', and no charset
26
+ # parameter is set, a charset of UTF-8 will be assumed. In all other
27
+ # cases, the 'content-type' header is used directly.
28
+ # @return [String] content-type and possibly charset of frame's body
29
+ def determine_content_type(frame)
30
+ ct = frame[:'content-type']
31
+ if ct =~ /^text\//i && !(ct =~ /\;\s*charset=\"?[a-zA-Z0-9!\#$&.+\-^_]+\"?/i)
32
+ "#{ct};charset=UTF-8"
33
+ else
34
+ ct
35
+ end
36
+ end
37
+
38
+ # Determines the content-length of a frame being sent to a broker. For
39
+ # Ruby 1.8.7, this is just the +size+ of the frame's body.
40
+ # @return [Fixnum] byte-length of frame's body
41
+ def determine_content_length(frame)
42
+ frame.body.size
43
+ end
44
+
45
+ # Reads a line of text from the underlying IO. As per the Stomp 1.1
46
+ # specification, all header strings are encoded with UTF-8.
47
+ # @return [String] line of text read from IO, or '' if no text was read.
48
+ def get_header_line
49
+ @io.gets || ''
50
+ end
51
+ end
52
+
53
+ ::Stomper::FrameSerializer.__send__(:include, ::Stomper::Support::Ruby1_8::FrameSerializer)
@@ -0,0 +1,183 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ # The implementation of the {Stomper::Headers} class for Ruby 1.8.7
4
+ module Stomper::Support::Ruby1_8::Headers
5
+ # An array of header names ordered by when they were set.
6
+ # @return [Array<String>]
7
+ attr_reader :names
8
+
9
+ # Creates a new headers collection, initialized with the optional hash
10
+ # parameter.
11
+ # @note With Ruby 1.8.7, the order of hash keys may not be preserved
12
+ # @param [Hash] headers
13
+ # @see #merge!
14
+ def initialize(headers={})
15
+ @values = {}
16
+ @names = []
17
+ merge! headers
18
+ end
19
+
20
+ # Merges a hash into this collection of headers. All of the keys used
21
+ # in the hash must be convertable to Symbols through +to_sym+.
22
+ # @note With Ruby 1.8.7, the order of hash keys may not be preserved
23
+ # @param [Hash] hash
24
+ def merge!(hash)
25
+ hash.each { |k, v| self[k]= v }
26
+ end
27
+
28
+ # Reverse merges a hash into this collection of headers. The hash keys and
29
+ # values are included only if the headers collection does not already have
30
+ # a matching key. All of the keys used
31
+ # in the hash must be convertable to Symbols through +to_sym+.
32
+ # @note With Ruby 1.8.7, the order of hash keys may not be preserved
33
+ # @param [Hash] hash
34
+ def reverse_merge!(hash)
35
+ hash.each { |k, v|
36
+ self[k]= v unless has?(k)
37
+ }
38
+ end
39
+
40
+ # Returns true if a header value has been set for the supplied header name.
41
+ # @param [Object] name the header name to test (will be converted using +to_sym+)
42
+ # @return [Boolean] true if the specified header name has been set, otherwise false.
43
+ # @example
44
+ # header.has? 'content-type' #=> true
45
+ # header.key? 'unset header' #=> false
46
+ # header.include? 'content-length' #=> true
47
+ def has?(name)
48
+ @values.key?(name.to_sym)
49
+ end
50
+ alias :key? :has?
51
+ alias :include? :has?
52
+
53
+ # Retrieves all header values associated with the supplied header name.
54
+ # In general, this will be an array containing only the principle header
55
+ # value; however, in the event a frame contained repeated header names,
56
+ # this method will return all of the associated values. The first
57
+ # element of the array will be the principle value of the supplied
58
+ # header name.
59
+ #
60
+ # @param [Object] name the header name associated with the desired values (will be converted using +to_sym+)
61
+ # @return [Array] the array of values associated with the header name.
62
+ # @example
63
+ # headers.all_values('content-type') #=> [ 'text/plain' ]
64
+ # headers.all(:repeated_header) #=> [ 'principle value', '13', 'other value']
65
+ # headers['name'] == headers.all(:name).first #=> true
66
+ def all_values(name)
67
+ @values[name.to_sym] || []
68
+ end
69
+ alias :all :all_values
70
+
71
+ # Deletes all of the header values associated with the header name and
72
+ # removes the header name itself. This is analogous to the +delete+
73
+ # method found in Hash objects.
74
+ #
75
+ # @param [Object] name the header name to remove from this collection (will be converted using +to_sym+)
76
+ # @return [Array] the array of values associated with the deleted header, or +nil+ if the header name did not exist
77
+ # @example
78
+ # headers.delete(:'content-type') #=> [ 'text/html' ]
79
+ # headers.delete('no such header') #=> nil
80
+ def delete(name)
81
+ name = name.to_sym
82
+ if @values.key? name
83
+ @names.delete(name)
84
+ @values.delete(name)
85
+ end
86
+ end
87
+
88
+ # Appends a header value to the specified header name. If the specified
89
+ # header name is not known, the supplied value will also become the
90
+ # principle value. This method is used internally when constructing
91
+ # frames sent by the broker to capture repeated header names.
92
+ #
93
+ # @param [Object] name the header name to associate with the supplied value (will be converted using +to_s+)
94
+ # @param [Object] val the header value to associate with the supplied name (will be converted using +to_s+)
95
+ # @return [String] the supplied value as a string.
96
+ # @example
97
+ # headers.append(:'new header', 'first value') #=> 'first value'
98
+ # headers.append('new header', nil) #=> ''
99
+ # headers.append('new header', 13) #=> '13'
100
+ # headers['new header'] #=> 'first value'
101
+ # headers.all('new header') #=> ['first value', '', '13']
102
+ def append(name, val)
103
+ name = name.to_sym
104
+ val = val.to_s
105
+ if @values.key?(name)
106
+ @values[name] << val
107
+ else
108
+ self[name]= val
109
+ end
110
+ val
111
+ end
112
+
113
+ # Gets the principle header value paired with the supplied header name. The name will
114
+ # be converted to a Symbol, so must respond to the +to_sym+ method. The
115
+ # Stomp 1.1 protocol specifies that in the event of a repeated header name,
116
+ # the first value encountered serves as the principle value.
117
+ #
118
+ # @param [Object] name the header name paired with the desired value (will be converted using +to_sym+)
119
+ # @return [String] the value associated with the requested header name
120
+ # @return [nil] if no value has been set for the associated header name
121
+ # @example
122
+ # headers['content-type'] #=> 'text/plain'
123
+ def [](name)
124
+ vals = @values[name.to_sym]
125
+ vals && vals.first
126
+ end
127
+
128
+ # Sets the header value paired with the supplied header name. The name
129
+ # will be converted to a Symbol and must respond to +to_sym+; meanwhile,
130
+ # the value will be converted to a String so must respond to +to_s+.
131
+ # Setting a header value in this fashion will overwrite any repeated header values.
132
+ #
133
+ # @param [Object] name the header name to associate with the supplied value (will be converted using +to_sym+)
134
+ # @param [Object] val the value to pair with the supplied name (will be converted using +to_s+)
135
+ # @return [String] the supplied value as a string.
136
+ # @example
137
+ # headers['content-type'] = 'image/png' #=> 'image/png'
138
+ # headers[:'content-type'] = nil #=> ''
139
+ # headers['content-type'] #=> ''
140
+ def []=(name, val)
141
+ name = name.to_sym
142
+ val = val.to_s
143
+ @names << name unless @values.key?(name)
144
+ @values[name] = [val]
145
+ val
146
+ end
147
+
148
+ # Iterates over each header name / value pair in the order in which the
149
+ # headers names were set. If this collection contains repeated header names,
150
+ # the supplied block will receive those header names repeatedly, once
151
+ # for each value. If no block is supplied, then an +Enumerator+ is returned.
152
+ # All header names yielded through this method will be Strings and not
153
+ # the Symbol keys used internally.
154
+ #
155
+ # @yield [name, value] a header name and an associated value
156
+ # @yieldparam [String] name a header name
157
+ # @yieldparam [String] value a value associated with the header
158
+ # @return [Headers] +self+ if a block was supplied
159
+ # @return [Enumerable::Enumerator] a collection enumerator if no block was supplied
160
+ # @example
161
+ # headers['name 1'] = 'value 1'
162
+ # headers.append('name_2', 'value 2')
163
+ # headers.append(:name_2, 42)
164
+ # headers.each { |name, val| p "#{name} - #{v}" }
165
+ # # name 1 - value 1
166
+ # # name_2 - value 2
167
+ # # name_2 - 42
168
+ # #=> #<Stomper::Components::Headers:0x0000010289d6d8>
169
+ def each(&block)
170
+ if block_given?
171
+ @names.each do |name|
172
+ @values[name].each do |val|
173
+ yield [name.to_s, val]
174
+ end
175
+ end
176
+ self
177
+ else
178
+ ::Enumerable::Enumerator.new(self)
179
+ end
180
+ end
181
+ end
182
+
183
+ ::Stomper::Headers.__send__(:include, ::Stomper::Support::Ruby1_8::Headers)
@@ -0,0 +1,64 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ # Implementation of {Stomper::FrameSerializer} methods for Ruby 1.9
4
+ module Stomper::Support::Ruby1_9::FrameSerializer
5
+ # Return the body with the specified encoding applied. An encoding
6
+ # is specified in the 'content-type' header of a frame by the +charset+
7
+ # parameter. If no encoding was explicitly specified, a UTF-8 encoding
8
+ # is used when the frame's content type begins with +text/+, otherwise
9
+ # an encoding of US-ASCII is used.
10
+ # @param [String] body body of message to encode
11
+ # @param [String] ct content-type header of frame
12
+ # @return [String] body with the appropriate encoding applied
13
+ def encode_body(body, ct_header)
14
+ body.tap do |b|
15
+ charset = ct_header ?
16
+ (ct_header =~ /\;\s*charset=\"?([\w\-]+)\"?/i) ? $1 :
17
+ (ct_header =~ /^text\//) ? 'UTF-8' : 'ASCII-8BIT' :
18
+ 'ASCII-8BIT'
19
+ b.force_encoding(charset)
20
+ end
21
+ end
22
+
23
+ # Reads a single byte from the body portion of a frame. Returns +nil+ if
24
+ # the character read is a {::Stomper::FrameSerializer::FRAME_TERMINATOR frame terminator}.
25
+ # @return [String,nil] the byte read from the stream.
26
+ def get_body_byte
27
+ #raise "Implementation varies by Ruby version"
28
+ c = @io.getc
29
+ c == ::Stomper::FrameSerializer::FRAME_TERMINATOR ? nil : c
30
+ end
31
+
32
+ # Determines the content-type of a frame being sent to a broker. This
33
+ # version of the method is used by Ruby 1.9, and will use the encoding
34
+ # of the frame's body to help determine the appropriate charset. If the
35
+ # body's encoding is binary, no charset parameter will be included (even
36
+ # if one was manually set.) Otherwise, if the content-type is ommitted,
37
+ # a value of 'text/plain' is assumed and the encoding of the body is
38
+ # used as the charset parameter.
39
+ # @return [String] content-type and possibly charset of frame's body
40
+ def determine_content_type(frame)
41
+ ct = frame[:'content-type']
42
+ ct &&= ct.gsub(/\;\s*charset=\"?[a-zA-Z0-9!\#$&.+\-^_]+\"?/i, '')
43
+ enc = frame.body.encoding.name
44
+ text = (enc != 'ASCII-8BIT') || ct =~ /^text\//
45
+ ct = 'text/plain' if ct.nil? && text
46
+ ct && text ? "#{ct};charset=#{enc}" : ct
47
+ end
48
+
49
+ # Determines the content-length of a frame being sent to a broker. For
50
+ # Ruby 1.9, this is the +bytesize+ of the frame's body.
51
+ # @return [Fixnum] byte-length of frame's body
52
+ def determine_content_length(frame)
53
+ frame.body.bytesize
54
+ end
55
+
56
+ # Reads a line of text from the underlying IO. As per the Stomp 1.1
57
+ # specification, all header strings are encoded with UTF-8.
58
+ # @return [String] line of text read from IO, or '' if no text was read.
59
+ def get_header_line
60
+ (@io.gets || '').tap { |line| line.force_encoding('UTF-8') }
61
+ end
62
+ end
63
+
64
+ ::Stomper::FrameSerializer.__send__(:include, ::Stomper::Support::Ruby1_9::FrameSerializer)
@@ -0,0 +1,172 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ # The implementation of the {Stomper::Headers} class for Ruby 1.9
4
+ module Stomper::Support::Ruby1_9::Headers
5
+ # Creates a new headers collection, initialized with the optional hash
6
+ # parameter.
7
+ # @param [Hash] headers
8
+ # @see #merge!
9
+ def initialize(headers={})
10
+ @values = {}
11
+ merge! headers
12
+ end
13
+
14
+ # Merges a hash into this collection of headers. All of the keys used
15
+ # in the hash must be convertable to Symbols through +to_sym+.
16
+ # @param [Hash] hash
17
+ def merge!(hash)
18
+ hash.each { |k, v| self[k]= v }
19
+ end
20
+
21
+ # Reverse merges a hash into this collection of headers. The hash keys and
22
+ # values are included only if the headers collection does not already have
23
+ # a matching key. All of the keys used
24
+ # in the hash must be convertable to Symbols through +to_sym+.
25
+ # @param [Hash] hash
26
+ def reverse_merge!(hash)
27
+ hash.each { |k, v|
28
+ self[k]= v unless has?(k)
29
+ }
30
+ end
31
+
32
+ # Returns true if a header value has been set for the supplied header name.
33
+ # @param [Object] name the header name to test (will be converted using +to_sym+)
34
+ # @return [Boolean] true if the specified header name has been set, otherwise false.
35
+ # @example
36
+ # header.has? 'content-type' #=> true
37
+ # header.key? 'unset header' #=> false
38
+ # header.include? 'content-length' #=> true
39
+ def has?(name)
40
+ @values.key? name.to_sym
41
+ end
42
+ alias :key? :has?
43
+ alias :include? :has?
44
+
45
+ # Retrieves all header values associated with the supplied header name.
46
+ # In general, this will be an array containing only the principle header
47
+ # value; however, in the event a frame contained repeated header names,
48
+ # this method will return all of the associated values. The first
49
+ # element of the array will be the principle value of the supplied
50
+ # header name.
51
+ #
52
+ # @param [Object] name the header name associated with the desired values (will be converted using +to_sym+)
53
+ # @return [Array] the array of values associated with the header name.
54
+ # @example
55
+ # headers.all_values('content-type') #=> [ 'text/plain' ]
56
+ # headers.all(:repeated_header) #=> [ 'principle value', '13', 'other value']
57
+ # headers['name'] == headers.all(:name).first #=> true
58
+ def all_values(name)
59
+ @values[name.to_sym] || []
60
+ end
61
+ alias :all :all_values
62
+
63
+ # Deletes all of the header values associated with the header name and
64
+ # removes the header name itself. This is analogous to the +delete+
65
+ # method found in Hash objects.
66
+ #
67
+ # @param [Object] name the header name to remove from this collection (will be converted using +to_sym+)
68
+ # @return [Array] the array of values associated with the deleted header, or +nil+ if the header name did not exist
69
+ # @example
70
+ # headers.delete(:'content-type') #=> [ 'text/html' ]
71
+ # headers.delete('no such header') #=> nil
72
+ def delete(name)
73
+ @values.delete name.to_sym
74
+ end
75
+
76
+ # Appends a header value to the specified header name. If the specified
77
+ # header name is not known, the supplied value will also become the
78
+ # principle value. This method is used internally when constructing
79
+ # frames sent by the broker to capture repeated header names.
80
+ #
81
+ # @param [Object] name the header name to associate with the supplied value (will be converted using +to_s+)
82
+ # @param [Object] val the header value to associate with the supplied name (will be converted using +to_s+)
83
+ # @return [String] the supplied value as a string.
84
+ # @example
85
+ # headers.append(:'new header', 'first value') #=> 'first value'
86
+ # headers.append('new header', nil) #=> ''
87
+ # headers.append('new header', 13) #=> '13'
88
+ # headers['new header'] #=> 'first value'
89
+ # headers.all('new header') #=> ['first value', '', '13']
90
+ def append(name, val)
91
+ name = name.to_sym
92
+ val = val.to_s
93
+ @values[name] ||= []
94
+ @values[name] << val
95
+ val
96
+ end
97
+
98
+ # Gets the principle header value paired with the supplied header name. The name will
99
+ # be converted to a Symbol, so must respond to the +to_sym+ method. The
100
+ # Stomp 1.1 protocol specifies that in the event of a repeated header name,
101
+ # the first value encountered serves as the principle value.
102
+ #
103
+ # @param [Object] name the header name paired with the desired value (will be converted using +to_sym+)
104
+ # @return [String] the value associated with the requested header name
105
+ # @return [nil] if no value has been set for the associated header name
106
+ # @example
107
+ # headers['content-type'] #=> 'text/plain'
108
+ def [](name)
109
+ name = name.to_sym
110
+ @values[name] && @values[name].first
111
+ end
112
+
113
+ # Sets the header value paired with the supplied header name. The name
114
+ # will be converted to a Symbol and must respond to +to_sym+; meanwhile,
115
+ # the value will be converted to a String so must respond to +to_s+.
116
+ # Setting a header value in this fashion will overwrite any repeated header values.
117
+ #
118
+ # @param [Object] name the header name to associate with the supplied value (will be converted using +to_sym+)
119
+ # @param [Object] val the value to pair with the supplied name (will be converted using +to_s+)
120
+ # @return [String] the supplied value as a string.
121
+ # @example
122
+ # headers['content-type'] = 'image/png' #=> 'image/png'
123
+ # headers[:'content-type'] = nil #=> ''
124
+ # headers['content-type'] #=> ''
125
+ def []=(name, val)
126
+ name = name.to_sym
127
+ val = val.to_s
128
+ @values[name] = [val]
129
+ val
130
+ end
131
+
132
+ # Iterates over each header name / value pair in the order in which the
133
+ # headers names were set. If this collection contains repeated header names,
134
+ # the supplied block will receive those header names repeatedly, once
135
+ # for each value. If no block is supplied, then an +Enumerator+ is returned.
136
+ # All header names yielded through this method will be Strings and not
137
+ # the Symbol keys used internally.
138
+ #
139
+ # @yield [name, value] a header name and an associated value
140
+ # @yieldparam [String] name a header name
141
+ # @yieldparam [String] value a value associated with the header
142
+ # @return [Headers] +self+ if a block was supplied
143
+ # @return [Enumerator] a collection enumerator if no block was supplied
144
+ # @example
145
+ # headers['name 1'] = 'value 1'
146
+ # headers.append('name_2', 'value 2')
147
+ # headers.append(:name_2, 42)
148
+ # headers.each { |name, val| p "#{name} - #{v}" }
149
+ # # name 1 - value 1
150
+ # # name_2 - value 2
151
+ # # name_2 - 42
152
+ # #=> #<Stomper::Components::Headers:0x0000010289d6d8>
153
+ def each(&block)
154
+ if block_given?
155
+ @values.each do |name, vals|
156
+ name_str = name.to_s
157
+ vals.each do |val|
158
+ yield [name_str, val]
159
+ end
160
+ end
161
+ self
162
+ else
163
+ ::Enumerator.new(self)
164
+ end
165
+ end
166
+
167
+ # An array of header names ordered by when they were set.
168
+ # @return [Array<String>]
169
+ def names; @values.keys; end
170
+ end
171
+
172
+ ::Stomper::Headers.__send__(:include, ::Stomper::Support::Ruby1_9::Headers)