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