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.
- data/.gitignore +5 -0
- data/{spec/spec.opts → .rspec} +0 -2
- data/Gemfile +4 -0
- data/LICENSE +201 -201
- data/README.md +130 -0
- data/Rakefile +5 -0
- data/examples/basic.rb +38 -0
- data/examples/events.rb +54 -0
- data/features/acking_messages.feature +147 -0
- data/features/disconnecting.feature +12 -0
- data/features/establish_connection.feature +44 -0
- data/features/protocol_version_negotiation.feature +61 -0
- data/features/receipts.feature +72 -0
- data/features/scopes.feature +32 -0
- data/features/secure_connections.feature +38 -0
- data/features/send_and_message.feature +28 -0
- data/features/steps/acking_messages_steps.rb +39 -0
- data/features/steps/disconnecting_steps.rb +8 -0
- data/features/steps/establish_connection_steps.rb +74 -0
- data/features/steps/frame_transmission_steps.rb +35 -0
- data/features/steps/protocol_version_negotiation_steps.rb +15 -0
- data/features/steps/receipts_steps.rb +79 -0
- data/features/steps/scopes_steps.rb +52 -0
- data/features/steps/secure_connections_steps.rb +41 -0
- data/features/steps/send_and_message_steps.rb +35 -0
- data/features/steps/subscribing_steps.rb +36 -0
- data/features/steps/threaded_receiver_steps.rb +8 -0
- data/features/steps/transactions_steps.rb +0 -0
- data/features/subscribing.feature +151 -0
- data/features/support/env.rb +11 -0
- data/features/support/header_helpers.rb +12 -0
- data/features/support/ssl/README +6 -0
- data/features/support/ssl/broker_cert.csr +17 -0
- data/features/support/ssl/broker_cert.pem +72 -0
- data/features/support/ssl/broker_key.pem +27 -0
- data/features/support/ssl/client_cert.csr +17 -0
- data/features/support/ssl/client_cert.pem +72 -0
- data/features/support/ssl/client_key.pem +27 -0
- data/features/support/ssl/demoCA/cacert.pem +17 -0
- data/features/support/ssl/demoCA/index.txt +2 -0
- data/features/support/ssl/demoCA/index.txt.attr +1 -0
- data/features/support/ssl/demoCA/index.txt.attr.old +1 -0
- data/features/support/ssl/demoCA/index.txt.old +1 -0
- data/features/support/ssl/demoCA/newcerts/01.pem +72 -0
- data/features/support/ssl/demoCA/newcerts/02.pem +72 -0
- data/features/support/ssl/demoCA/private/cakey.pem +17 -0
- data/features/support/ssl/demoCA/serial +1 -0
- data/features/support/ssl/demoCA/serial.old +1 -0
- data/features/support/test_stomp_server.rb +150 -0
- data/features/threaded_receiver.feature +11 -0
- data/features/transactions.feature +66 -0
- data/lib/stomper.rb +30 -20
- data/lib/stomper/connection.rb +442 -102
- data/lib/stomper/errors.rb +59 -0
- data/lib/stomper/extensions.rb +10 -0
- data/lib/stomper/extensions/common.rb +258 -0
- data/lib/stomper/extensions/events.rb +213 -0
- data/lib/stomper/extensions/heartbeat.rb +101 -0
- data/lib/stomper/extensions/scoping.rb +56 -0
- data/lib/stomper/frame.rb +54 -0
- data/lib/stomper/frame_serializer.rb +217 -0
- data/lib/stomper/headers.rb +15 -0
- data/lib/stomper/receipt_manager.rb +36 -0
- data/lib/stomper/receivers.rb +7 -0
- data/lib/stomper/receivers/threaded.rb +71 -0
- data/lib/stomper/scopes.rb +9 -0
- data/lib/stomper/scopes/header_scope.rb +49 -0
- data/lib/stomper/scopes/receipt_scope.rb +44 -0
- data/lib/stomper/scopes/transaction_scope.rb +109 -0
- data/lib/stomper/sockets.rb +66 -28
- data/lib/stomper/subscription_manager.rb +79 -0
- data/lib/stomper/support.rb +68 -0
- data/lib/stomper/support/1.8/frame_serializer.rb +53 -0
- data/lib/stomper/support/1.8/headers.rb +183 -0
- data/lib/stomper/support/1.9/frame_serializer.rb +64 -0
- data/lib/stomper/support/1.9/headers.rb +172 -0
- data/lib/stomper/support/ruby.rb +13 -0
- data/lib/stomper/uris.rb +49 -0
- data/lib/stomper/version.rb +7 -0
- data/spec/spec_helper.rb +13 -9
- data/spec/stomper/connection_spec.rb +712 -0
- data/spec/stomper/extensions/common_spec.rb +187 -0
- data/spec/stomper/extensions/events_spec.rb +78 -0
- data/spec/stomper/extensions/heartbeat_spec.rb +103 -0
- data/spec/stomper/extensions/scoping_spec.rb +21 -0
- data/spec/stomper/frame_serializer_1.8_spec.rb +318 -0
- data/spec/stomper/frame_serializer_spec.rb +316 -0
- data/spec/stomper/frame_spec.rb +36 -0
- data/spec/stomper/headers_spec.rb +224 -0
- data/spec/stomper/receipt_manager_spec.rb +91 -0
- data/spec/stomper/receivers/threaded_spec.rb +116 -0
- data/spec/stomper/scopes/header_scope_spec.rb +42 -0
- data/spec/stomper/scopes/receipt_scope_spec.rb +51 -0
- data/spec/stomper/scopes/transaction_scope_spec.rb +183 -0
- data/spec/stomper/sockets_spec.rb +113 -0
- data/spec/stomper/subscription_manager_spec.rb +107 -0
- data/spec/stomper/support_spec.rb +69 -0
- data/spec/stomper/uris_spec.rb +54 -0
- data/spec/stomper_spec.rb +9 -0
- data/spec/support/custom_argument_matchers.rb +57 -0
- data/spec/support/existential_frame_matchers.rb +19 -0
- data/spec/support/frame_header_matchers.rb +10 -0
- data/stomper.gemspec +30 -0
- metadata +272 -97
- data/AUTHORS +0 -21
- data/CHANGELOG +0 -20
- data/README.rdoc +0 -120
- data/lib/stomper/client.rb +0 -34
- data/lib/stomper/frame_reader.rb +0 -73
- data/lib/stomper/frame_writer.rb +0 -21
- data/lib/stomper/frames.rb +0 -39
- data/lib/stomper/frames/abort.rb +0 -10
- data/lib/stomper/frames/ack.rb +0 -25
- data/lib/stomper/frames/begin.rb +0 -11
- data/lib/stomper/frames/client_frame.rb +0 -89
- data/lib/stomper/frames/commit.rb +0 -10
- data/lib/stomper/frames/connect.rb +0 -10
- data/lib/stomper/frames/connected.rb +0 -30
- data/lib/stomper/frames/disconnect.rb +0 -10
- data/lib/stomper/frames/error.rb +0 -21
- data/lib/stomper/frames/message.rb +0 -48
- data/lib/stomper/frames/receipt.rb +0 -19
- data/lib/stomper/frames/send.rb +0 -10
- data/lib/stomper/frames/server_frame.rb +0 -38
- data/lib/stomper/frames/subscribe.rb +0 -42
- data/lib/stomper/frames/unsubscribe.rb +0 -19
- data/lib/stomper/open_uri_interface.rb +0 -41
- data/lib/stomper/receipt_handlers.rb +0 -23
- data/lib/stomper/receiptor.rb +0 -38
- data/lib/stomper/subscriber.rb +0 -76
- data/lib/stomper/subscription.rb +0 -128
- data/lib/stomper/subscriptions.rb +0 -95
- data/lib/stomper/threaded_receiver.rb +0 -59
- data/lib/stomper/transaction.rb +0 -185
- data/lib/stomper/transactor.rb +0 -50
- data/lib/stomper/uri.rb +0 -55
- data/spec/client_spec.rb +0 -29
- data/spec/connection_spec.rb +0 -22
- data/spec/frame_reader_spec.rb +0 -37
- data/spec/frame_writer_spec.rb +0 -27
- data/spec/frames/client_frame_spec.rb +0 -66
- data/spec/frames/indirect_frame_spec.rb +0 -45
- data/spec/frames/server_frame_spec.rb +0 -85
- data/spec/open_uri_interface_spec.rb +0 -132
- data/spec/receiptor_spec.rb +0 -35
- data/spec/shared_connection_examples.rb +0 -79
- data/spec/subscriber_spec.rb +0 -77
- data/spec/subscription_spec.rb +0 -157
- data/spec/subscriptions_spec.rb +0 -145
- data/spec/threaded_receiver_spec.rb +0 -33
- data/spec/transaction_spec.rb +0 -139
- 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)
|