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,101 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ # Provides the heart-beating interface for a {Stomper::Connection} object.
4
+ module Stomper::Extensions::Heartbeat
5
+ # Extends an object with any additional modules that are appropriate
6
+ # for the Stomp protocol being used.
7
+ def self.extend_by_protocol_version(instance, version)
8
+ if EXTEND_BY_VERSION[version]
9
+ EXTEND_BY_VERSION[version].each do |mod|
10
+ instance.extend mod
11
+ end
12
+ end
13
+ end
14
+
15
+ # By default, this method does nothing. If the established connection
16
+ # utilizes the Stomp 1.1 protocol, this method will be overridden by
17
+ # {Stomper::Protocols::V1_1::Heartbeating#beat}.
18
+ def beat; end
19
+
20
+ # By default, a connection is alive if it is connected.
21
+ # If the established connection utilizes the Stomp 1.1 protocol, this
22
+ # method will be overridden by {Stomper::Protocols::V1_1::Heartbeating#alive?}.
23
+ # @return [true,false]
24
+ # @see #dead?
25
+ def alive?
26
+ connected?
27
+ end
28
+
29
+ # A {Stomper::Connection connection} is dead if it is not +alive?+
30
+ # @return [true, false]
31
+ # @see #alive?
32
+ def dead?
33
+ !alive?
34
+ end
35
+
36
+ # Stomp Protocol 1.1 extensions to the heart-beating interface
37
+ module V1_1
38
+ # Send a heartbeat to the broker
39
+ def beat
40
+ transmit ::Stomper::Frame.new
41
+ end
42
+
43
+ # Stomp 1.1 {Stomper::Connection connections} are alive if they are
44
+ # +connected?+ and are meeting their negotiated heart-beating obligations.
45
+ # @return [true, false]
46
+ # @see #dead?
47
+ def alive?
48
+ connected? && client_alive? && broker_alive?
49
+ end
50
+
51
+ # Maximum number of milliseconds that can pass between frame / heartbeat
52
+ # transmissions before we consider the client to be dead.
53
+ # @return [Fixnum]
54
+ def heartbeat_client_limit
55
+ unless defined?(@heartbeat_client_limit)
56
+ @heartbeat_client_limit = heartbeating[0] > 0 ? (1.1 * heartbeating[0]) : 0
57
+ end
58
+ @heartbeat_client_limit
59
+ end
60
+
61
+ # Maximum number of milliseconds that can pass between frames / heartbeats
62
+ # received before we consider the broker to be dead.
63
+ # @return [Fixnum]
64
+ def heartbeat_broker_limit
65
+ unless defined?(@heartbeat_broker_limit)
66
+ @heartbeat_broker_limit = heartbeating[1] > 0 ? (1.1 * heartbeating[1]) : 0
67
+ end
68
+ @heartbeat_broker_limit
69
+ end
70
+
71
+ # Returns true if the client is alive. Client is alive if client heartbeating
72
+ # is disabled, or the number of milliseconds that have passed since last
73
+ # transmission is less than or equal to {#heartbeat_client_limit client} limit
74
+ # @return [true,false]
75
+ # @see #heartbeat_client_limit
76
+ # @see #broker_alive?
77
+ def client_alive?
78
+ # Consider some benchmarking to determine if this is faster than
79
+ # re-writing the method after its first invocation.
80
+ heartbeat_client_limit == 0 ||
81
+ duration_since_transmitted <= heartbeat_client_limit
82
+ end
83
+
84
+ # Returns true if the broker is alive. Broker is alive if broker heartbeating
85
+ # is disabled, or the number of milliseconds that have passed since last
86
+ # receiving is less than or equal to {#heartbeat_broker_limit broker} limit
87
+ # @return [true,false]
88
+ # @see #heartbeat_broker_limit
89
+ # @see #client_alive?
90
+ def broker_alive?
91
+ heartbeat_broker_limit == 0 ||
92
+ duration_since_received <= heartbeat_broker_limit
93
+ end
94
+ end
95
+
96
+ # A mapping between protocol versions and modules to include
97
+ EXTEND_BY_VERSION = {
98
+ '1.0' => [ ],
99
+ '1.1' => [ ::Stomper::Extensions::Heartbeat::V1_1 ]
100
+ }
101
+ end
@@ -0,0 +1,56 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ # Provides the scoping interface for a {Stomper::Connection} object.
4
+ module Stomper::Extensions::Scoping
5
+ # Creates a new {Stomper::Scopes::TransactionScope} to perform
6
+ # a transaction. If a block is provided, all SEND, ACK, NACK, COMMIT and
7
+ # ABORT frames generated within the block are bound to the same transaction.
8
+ # Further, if an exception is raised within the block, the transaction is
9
+ # rolled back through an ABORT frame, otherwise it is automatically committed
10
+ # through a COMMIT frame. If a block is not provided, the transaction must
11
+ # be manually aborted or committed through the returned
12
+ # {Stomper::Scopes::TransactionScope} object.
13
+ # @param [{Symbol => Object}] headers
14
+ # @yield [tx] block is evaluated as a transaction
15
+ # @yieldparam [Stomper::Scopes::TransactionScope] tx
16
+ # @return [Stomper::Scopes::TransactionScope]
17
+ # @example Gonna need an example or two
18
+ def with_transaction(headers={}, &block)
19
+ create_scope(::Stomper::Scopes::TransactionScope, headers, block)
20
+ end
21
+
22
+ # Creates a new {Stomper::Scopes::ReceiptScope} using
23
+ # a supplied block as the receipt handler. If no block is provided, no
24
+ # receipt handler is created; however, all frames generated through this
25
+ # {Stomper::Scopes::ReceiptScope} will still request a RECEIPT
26
+ # from the broker.
27
+ # @param [{Symbol => Object}] headers
28
+ # @yield [receipt] callback invoked upon receiving the RECEIPT frame
29
+ # @yieldparam [Stomper::Frame] the received RECEIPT frame
30
+ # @return [Stomper::Scopes::ReceiptScope]
31
+ # @example Gonna need an example or two
32
+ # @see Stomper::Extensions::Events#on_receipt}
33
+ def with_receipt(headers={}, &block)
34
+ create_scope(::Stomper::Scopes::ReceiptScope, headers, block)
35
+ end
36
+
37
+ # Creates a new {Stomper::Scopes::HeaderScope} from the
38
+ # supplied hash of headers. If a block is provided, it will be invoked with
39
+ # with this {Stomper::Scopes::HeaderScope} as its only parameter.
40
+ # @param [{Symbol => Object}] headers
41
+ # @yield [header_scope] block is evaluated applying the specified headers to
42
+ # all frames generated within the block.
43
+ # @yieldparam [Stomper::Scopes::HeaderScope] header_scope
44
+ # @return [Stomper::Scopes::HeaderScope]
45
+ # @example Gonna need an example or two
46
+ def with_headers(headers, &block)
47
+ create_scope(::Stomper::Scopes::HeaderScope, headers, block)
48
+ end
49
+
50
+ def create_scope(klass, headers, callback)
51
+ klass.new(self, headers).tap do |scoped|
52
+ scoped.apply_to(callback)
53
+ end
54
+ end
55
+ private :create_scope
56
+ end
@@ -0,0 +1,54 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ # A generic encapsulation of a frame as specified by the Stomp protocol.
4
+ class Stomper::Frame
5
+ # The command name of this frame (CONNECTED, SEND, RECEIPT, etc.)
6
+ # @return [String]
7
+ attr_accessor :command
8
+
9
+ # The body of this frame
10
+ # @return [String] if a body has been set
11
+ attr_accessor :body
12
+
13
+ # The headers associated with this frame
14
+ # @return [Stomper::Headers]
15
+ attr_reader :headers
16
+
17
+ # Creates a new frame. The frame will be initialized with the optional
18
+ # +command+ name, a {Stomper::Headers headers} collection initialized
19
+ # with the optional +headers+ hash, and an optional body.
20
+ def initialize(command=nil, headers={}, body=nil)
21
+ @command = command
22
+ @headers = ::Stomper::Headers.new(headers)
23
+ @body = body
24
+ end
25
+
26
+ # Gets the header value paired with the supplied name. This is a convenient
27
+ # shortcut for `frame.headers[name]`.
28
+ #
29
+ # @param [Object] name the header name associated with the desired value
30
+ # @return [String] the value associated with the requested header name
31
+ # @see Stomper::Headers#[]
32
+ # @example
33
+ # frame['content-type'] #=> 'text/plain'
34
+ def [](name); @headers[name]; end
35
+
36
+ # Sets the header value paired with the supplied name. This is a convenient
37
+ # shortcut for `frame.headers[name] = val`.
38
+ #
39
+ # @param [Object] name the header name to associate with the supplied value
40
+ # @param [Object] val the value to associate with the supplied header name
41
+ # @return [String] the supplied value as a string, or `nil` if `nil` was supplied as the value.
42
+ # @see Stomper::Headers#[]=
43
+ # @example
44
+ # frame['content-type'] = 'text/plain' #=> 'text/plain'
45
+ # frame['other header'] = 42 #=> '42'
46
+ def []=(name, val); @headers[name] = val; end
47
+
48
+ # A convenience method for getting the 'content-type' header without
49
+ # any parameters.
50
+ # @return [String]
51
+ def content_type
52
+ @headers[:'content-type'] && @headers[:'content-type'].split(';').first || ''
53
+ end
54
+ end
@@ -0,0 +1,217 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ # This class serializes Stomp frames to IO streams. Submodule mixins within
4
+ # this class are used to adjust the serialization behavior depending upon
5
+ # the Stomp Protocol version being used.
6
+ # @see Stomper::Support::Ruby1_8::FrameSerializer Implementation for Ruby 1.8.7
7
+ # @see Stomper::Support::Ruby1_9::FrameSerializer Implementation for Ruby 1.9
8
+ class Stomper::FrameSerializer
9
+ # The character that must be present at the end of every Stomp frame.
10
+ FRAME_TERMINATOR = "\000"
11
+
12
+ # Creates a new frame serializer that will read {Stomper::Frame frames} from
13
+ # and write {Stomper::Frame frames} to the supplied IO object (typically
14
+ # a TCP or SSL socket.)
15
+ # @param [IO] io IO stream to read from and write to
16
+ def initialize(io)
17
+ @io = io
18
+ @write_mutex = ::Mutex.new
19
+ @read_mutex = ::Mutex.new
20
+ end
21
+
22
+ # Extends the serializer based on the version of the protocol being used.
23
+ # @param [String] version protocol version being used
24
+ # @return [self]
25
+ def extend_for_protocol(version)
26
+ if EXTEND_BY_VERSION[version]
27
+ EXTEND_BY_VERSION[version].each { |m| extend m }
28
+ end
29
+ self
30
+ end
31
+
32
+ # Serializes and writes a {Stomper::Frame} to the underlying IO stream. This
33
+ # includes setting the appropriate values for 'content-length' and
34
+ # 'content-type' headers, if applicable.
35
+ # @param [Stomper::Frame] frame
36
+ # @return [Stomper::Frame] the frame that was passed to the method
37
+ def write_frame(frame)
38
+ @write_mutex.synchronize { __write_frame__(frame) }
39
+ end
40
+
41
+ # Deserializes and returns a {Stomper::Frame} read from the underlying IO
42
+ # stream. If the IO stream produces data that violates the Stomp protocol
43
+ # specification, an instance of {Stomper::Errors::FatalProtocolError}, or
44
+ # one of its subclasses, will be raised.
45
+ # @return [Stomper::Frame]
46
+ # @raise [Stomper::Errors::MalformedFrameError] if the frame is not properly terminated
47
+ # @raise [Stomper::Errors::MalformedHeaderError] if a header is malformed (eg: contains no ':' separator)
48
+ def read_frame
49
+ @read_mutex.synchronize { __read_frame__ }
50
+ end
51
+
52
+ # Converts the headers of the supplied frame into a single "\n" delimited
53
+ # string that's suitable for writing to io.
54
+ # @param [Stomper::Frame] frame the frame whose headers should be serialized
55
+ # @return [String]
56
+ def serialize_headers(frame)
57
+ serialized = frame.headers.inject('') do |head_str, (k, v)|
58
+ k = escape_header_name(k)
59
+ next head_str if k.empty? || ['content-type', 'content-length'].include?(k)
60
+ head_str << "#{k}:#{escape_header_value(v)}\n"
61
+ end
62
+ if frame.body
63
+ if ct = determine_content_type(frame)
64
+ serialized << "content-type:#{ct}\n"
65
+ end
66
+ if clen = determine_content_length(frame)
67
+ serialized << "content-length:#{clen}\n"
68
+ end
69
+ end
70
+ serialized
71
+ end
72
+
73
+ # Escape a header name to comply with Stomp Protocol 1.0 specifications.
74
+ # All LF ("\n") and ":" characters are replaced with empty strings.
75
+ # @note If the connection is using the 1.1 protocol, this method will
76
+ # be overridden by {FrameSerializer::V1_1#escape_header_name}
77
+ # @param [String] str
78
+ # @return [String] escaped header name
79
+ def escape_header_name(str)
80
+ str.gsub(/[\n:]/, '')
81
+ end
82
+
83
+ # Escape a header value to comply with Stomp Protocol 1.0 specifications.
84
+ # All LF ("\n") characters are replaced with empty strings.
85
+ # @note If the connection is using the 1.1 protocol, this method will
86
+ # be overridden by {FrameSerializer::V1_1#escape_header_value}
87
+ # @param [String] str
88
+ # @return [String] escaped header value
89
+ def escape_header_value(str)
90
+ str.gsub(/\n/, '')
91
+ end
92
+
93
+ # Return the header name as it was passed. Stomp 1.0 does not provide
94
+ # any means for escaping special characters such as ":" and "\n"
95
+ # @note If the connection is using the 1.1 protocol, this method will
96
+ # be overridden by {FrameSerializer::V1_1#unescape_header_name}
97
+ # @param [String] str
98
+ # @return [String]
99
+ def unescape_header_name(str); str; end
100
+ alias :unescape_header_value :unescape_header_name
101
+
102
+ private
103
+ # These are the un-synchronized methods that do the real reading/writing
104
+ # of frames. This approach facilitates easier testing of Thread safety
105
+ def __write_frame__(frame)
106
+ if frame.command
107
+ @io.write [frame.command, "\n", serialize_headers(frame),
108
+ "\n", frame.body, FRAME_TERMINATOR].compact.join
109
+ else
110
+ @io.write "\n"
111
+ end
112
+ frame
113
+ end
114
+ def __read_frame__
115
+ command = @io.gets
116
+ return nil if command.nil?
117
+ command.chomp!
118
+ frame = Stomper::Frame.new
119
+ unless command.nil? || command.empty?
120
+ frame.command = command
121
+
122
+ while (header_line = get_header_line.chomp).length > 0
123
+ raise ::Stomper::Errors::MalformedHeaderError,
124
+ "unterminated header: '#{header_line}'" unless header_line.include? ':'
125
+ k, v = header_line.split(':', 2)
126
+ frame.headers.append(unescape_header_name(k), unescape_header_value(v))
127
+ end
128
+
129
+ body = nil
130
+ if frame[:'content-length'] && (len = frame[:'content-length'].to_i) > 0
131
+ body = @io.read len
132
+ raise ::Stomper::Errors::MalformedFrameError, "frame was not properly terminated" if get_body_byte
133
+ else
134
+ while (c = get_body_byte)
135
+ body ||= ""
136
+ body << c
137
+ end
138
+ end
139
+ frame.body = body && encode_body(body, frame[:'content-type'])
140
+ end
141
+ frame
142
+ end
143
+
144
+ # Stomp Protocol 1.1 specific frame writing / reading methods.
145
+ module V1_1
146
+ # Mapping of characters to their appropriate escape sequences. This
147
+ # is used when escaping headers for frames being written to the stream.
148
+ CHARACTER_ESCAPES = {
149
+ ':' => "\\c",
150
+ "\n" => "\\n",
151
+ "\\" => "\\\\"
152
+ }
153
+
154
+ # Mapping of escape sequences to their appropriate characters. This
155
+ # is used when unescaping headers being read from the stream.
156
+ ESCAPE_SEQUENCES = {
157
+ 'c' => ':',
158
+ '\\' => "\\",
159
+ 'n' => "\n"
160
+ }
161
+
162
+ # Escape a header name to comply with Stomp Protocol 1.1 specifications.
163
+ # All special characters (the keys of {CHARACTER_ESCAPES}) are replaced
164
+ # by their corresponding escape sequences.
165
+ # @param [String] str
166
+ # @return [String] escaped header name
167
+ def escape_header_name(hdr)
168
+ hdr.each_char.inject('') do |esc, ch|
169
+ esc << (CHARACTER_ESCAPES[ch] || ch)
170
+ end
171
+ end
172
+ alias :escape_header_value :escape_header_name
173
+
174
+ # Return the header name after known escape sequences have been
175
+ # translated to their respective values. The keys of {ESCAPE_SEQUENCES},
176
+ # prefixed with a '\' character, denote the allowed escape sequences.
177
+ # If an unknown escape sequence is encountered, an error is raised.
178
+ # @param [String] str header string to unescape
179
+ # @return [String] unescaped header string
180
+ # @raise [Stomper::Errors::InvalidHeaderEscapeSequenceError] if an
181
+ # unknown escape sequence is encountered within the string or if
182
+ # an escape sequence is not properly completed (the last character of
183
+ # +str+ is '\')
184
+ def unescape_header_name(str)
185
+ state = :read
186
+ str.each_char.inject('') do |unesc, ch|
187
+ case state
188
+ when :read
189
+ if ch == '\\'
190
+ state = :unescape
191
+ else
192
+ unesc << ch
193
+ end
194
+ when :unescape
195
+ state = :read
196
+ if ESCAPE_SEQUENCES[ch]
197
+ unesc << ESCAPE_SEQUENCES[ch]
198
+ else
199
+ raise ::Stomper::Errors::InvalidHeaderEscapeSequenceError,
200
+ "invalid header escape sequence encountered '\\#{ch}'"
201
+ end
202
+ end
203
+ unesc
204
+ end.tap do
205
+ raise ::Stomper::Errors::InvalidHeaderEscapeSequenceError,
206
+ "incomplete escape sequence encountered in '#{str}'" if state != :read
207
+ end
208
+ end
209
+ alias :unescape_header_value :unescape_header_name
210
+ end
211
+
212
+ # The modules to mix-in to {FrameSerializer} instances depending upon which
213
+ # protocol version is being used.
214
+ EXTEND_BY_VERSION = {
215
+ '1.1' => [ ::Stomper::FrameSerializer::V1_1 ]
216
+ }
217
+ end
@@ -0,0 +1,15 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ # A specialized container for storing header name / value pairs for a Stomp
4
+ # {Stomper::Frame Frame}. This container behaves much like a +Hash+, but
5
+ # is specialized for the Stomp protocol. Header names are always converted
6
+ # into +String+s through the use of +to_s+ and may have more than one value
7
+ # associated with them.
8
+ #
9
+ # @note Header names are case sensitive, therefore the names 'header'
10
+ # and 'Header' will not refer to the same values.
11
+ # @see Stomper::Support::Ruby1_8::Headers Implementation for Ruby 1.8.7
12
+ # @see Stomper::Support::Ruby1_9::Headers Implementation for Ruby 1.9
13
+ class Stomper::Headers
14
+ include ::Enumerable
15
+ end