stomper 1.0.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
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)