onstomp 1.0.0pre1
Sign up to get free protection for your applications and to get access to all the features.
- data/.autotest +2 -0
- data/.gitignore +6 -0
- data/.rspec +2 -0
- data/.yardopts +5 -0
- data/CHANGELOG.md +4 -0
- data/DeveloperNarrative.md +15 -0
- data/Gemfile +4 -0
- data/LICENSE.md +221 -0
- data/README.md +73 -0
- data/Rakefile +6 -0
- data/UserNarrative.md +8 -0
- data/examples/basic.rb +40 -0
- data/examples/events.rb +72 -0
- data/lib/onstomp/client.rb +152 -0
- data/lib/onstomp/components/frame.rb +108 -0
- data/lib/onstomp/components/frame_headers.rb +212 -0
- data/lib/onstomp/components/nil_processor.rb +20 -0
- data/lib/onstomp/components/scopes/header_scope.rb +25 -0
- data/lib/onstomp/components/scopes/receipt_scope.rb +25 -0
- data/lib/onstomp/components/scopes/transaction_scope.rb +191 -0
- data/lib/onstomp/components/scopes.rb +45 -0
- data/lib/onstomp/components/subscription.rb +30 -0
- data/lib/onstomp/components/threaded_processor.rb +62 -0
- data/lib/onstomp/components/uri.rb +30 -0
- data/lib/onstomp/components.rb +13 -0
- data/lib/onstomp/connections/base.rb +208 -0
- data/lib/onstomp/connections/heartbeating.rb +82 -0
- data/lib/onstomp/connections/serializers/stomp_1.rb +166 -0
- data/lib/onstomp/connections/serializers/stomp_1_0.rb +41 -0
- data/lib/onstomp/connections/serializers/stomp_1_1.rb +134 -0
- data/lib/onstomp/connections/serializers.rb +9 -0
- data/lib/onstomp/connections/stomp_1.rb +69 -0
- data/lib/onstomp/connections/stomp_1_0.rb +28 -0
- data/lib/onstomp/connections/stomp_1_1.rb +65 -0
- data/lib/onstomp/connections.rb +119 -0
- data/lib/onstomp/interfaces/client_configurable.rb +55 -0
- data/lib/onstomp/interfaces/client_events.rb +168 -0
- data/lib/onstomp/interfaces/connection_events.rb +62 -0
- data/lib/onstomp/interfaces/event_manager.rb +69 -0
- data/lib/onstomp/interfaces/frame_methods.rb +190 -0
- data/lib/onstomp/interfaces/receipt_manager.rb +33 -0
- data/lib/onstomp/interfaces/subscription_manager.rb +48 -0
- data/lib/onstomp/interfaces/uri_configurable.rb +106 -0
- data/lib/onstomp/interfaces.rb +14 -0
- data/lib/onstomp/version.rb +13 -0
- data/lib/onstomp.rb +147 -0
- data/onstomp.gemspec +29 -0
- data/spec/onstomp/client_spec.rb +265 -0
- data/spec/onstomp/components/frame_headers_spec.rb +163 -0
- data/spec/onstomp/components/frame_spec.rb +144 -0
- data/spec/onstomp/components/nil_processor_spec.rb +32 -0
- data/spec/onstomp/components/scopes/header_scope_spec.rb +27 -0
- data/spec/onstomp/components/scopes/receipt_scope_spec.rb +33 -0
- data/spec/onstomp/components/scopes/transaction_scope_spec.rb +227 -0
- data/spec/onstomp/components/scopes_spec.rb +63 -0
- data/spec/onstomp/components/subscription_spec.rb +58 -0
- data/spec/onstomp/components/threaded_processor_spec.rb +92 -0
- data/spec/onstomp/components/uri_spec.rb +33 -0
- data/spec/onstomp/connections/base_spec.rb +349 -0
- data/spec/onstomp/connections/heartbeating_spec.rb +132 -0
- data/spec/onstomp/connections/serializers/stomp_1_0_spec.rb +50 -0
- data/spec/onstomp/connections/serializers/stomp_1_1_spec.rb +99 -0
- data/spec/onstomp/connections/serializers/stomp_1_spec.rb +104 -0
- data/spec/onstomp/connections/stomp_1_0_spec.rb +54 -0
- data/spec/onstomp/connections/stomp_1_1_spec.rb +137 -0
- data/spec/onstomp/connections/stomp_1_spec.rb +113 -0
- data/spec/onstomp/connections_spec.rb +135 -0
- data/spec/onstomp/interfaces/client_events_spec.rb +108 -0
- data/spec/onstomp/interfaces/connection_events_spec.rb +55 -0
- data/spec/onstomp/interfaces/event_manager_spec.rb +72 -0
- data/spec/onstomp/interfaces/frame_methods_spec.rb +109 -0
- data/spec/onstomp/interfaces/receipt_manager_spec.rb +53 -0
- data/spec/onstomp/interfaces/subscription_manager_spec.rb +64 -0
- data/spec/onstomp_spec.rb +15 -0
- data/spec/spec_helper.rb +12 -0
- data/spec/support/custom_argument_matchers.rb +51 -0
- data/spec/support/frame_matchers.rb +88 -0
- data/spec/support/shared_frame_method_examples.rb +116 -0
- data/yard_extensions.rb +32 -0
- metadata +219 -0
@@ -0,0 +1,134 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
# Frame serializer / parser for STOMP 1.1 connections.
|
4
|
+
class OnStomp::Connections::Serializers::Stomp_1_1
|
5
|
+
include OnStomp::Connections::Serializers::Stomp_1
|
6
|
+
|
7
|
+
# Mapping of characters to their appropriate escape sequences. This
|
8
|
+
# is used when escaping headers for frames being written to the stream.
|
9
|
+
CHARACTER_ESCAPES = { ':' => "\\c", "\n" => "\\n", "\\" => "\\\\" }
|
10
|
+
|
11
|
+
# Mapping of escape sequences to their appropriate characters. This
|
12
|
+
# is used when unescaping headers being read from the stream.
|
13
|
+
ESCAPE_SEQUENCES = Hash[CHARACTER_ESCAPES.map { |k,v| [v,k] }]
|
14
|
+
|
15
|
+
# Creates a new serializer and calls {#reset_parser}
|
16
|
+
def initialize
|
17
|
+
reset_parser
|
18
|
+
end
|
19
|
+
|
20
|
+
# Converts a {OnStomp::Components::Frame frame} to a string
|
21
|
+
# @param [OnStomp::Components::Frame] frame
|
22
|
+
# @return [String]
|
23
|
+
def frame_to_string frame
|
24
|
+
frame_to_string_base(make_ct(frame)) do |k,v|
|
25
|
+
"#{escape_header k}:#{escape_header v}\n"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Escapes a header name or value by replacing special characters with
|
30
|
+
# their appropriate escape sequences. The header will also be encoded to
|
31
|
+
# 'UTF-8' when using Ruby 1.9+
|
32
|
+
# @param [String] s header name or value
|
33
|
+
# @return [String]
|
34
|
+
def escape_header s
|
35
|
+
encode_header(s).gsub(/[:\n\\\\]/) { |c| CHARACTER_ESCAPES[c] }
|
36
|
+
end
|
37
|
+
|
38
|
+
# Unescapes a header name or pair parsed from the read buffer by converting
|
39
|
+
# known escape sequences into their special characters. The header string
|
40
|
+
# will have a 'UTF-8' encoding forced upon it when using Ruby 1.9+, as per
|
41
|
+
# the STOMP 1.1 spec.
|
42
|
+
# @param [String] s header name or value
|
43
|
+
# @return [String]
|
44
|
+
# @raise [OnStomp::InvalidHeaderEscapeSequenceError]
|
45
|
+
# if an unknown escape sequence is present in the header name or value
|
46
|
+
def unescape_header s
|
47
|
+
force_header_encoding(s).gsub(/\\.?/) do |c|
|
48
|
+
ESCAPE_SEQUENCES[c] || raise(OnStomp::InvalidHeaderEscapeSequenceError, "#{c}")
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# Splits a header line into a header name / header value pair at the first
|
53
|
+
# ':' character {#unescape_header unescapes} them, and returns the pair.
|
54
|
+
# @param [String] str header line to split
|
55
|
+
# @return [[String, String]]
|
56
|
+
# @raise [OnStomp::MalformedHeaderError] if the header line
|
57
|
+
# lacks a ':' character
|
58
|
+
def split_header(str)
|
59
|
+
col = str.index(':')
|
60
|
+
unless col
|
61
|
+
raise OnStomp::MalformedHeaderError, "unterminated header: '#{str}'"
|
62
|
+
end
|
63
|
+
[ unescape_header(str[0...col]),
|
64
|
+
unescape_header(str[(col+1)..-1]) ]
|
65
|
+
end
|
66
|
+
|
67
|
+
# Forces the frame's body to match the charset specified in its +content-type+
|
68
|
+
# header, if applicable.
|
69
|
+
# @param [OnStomp::Components::Frame] frame
|
70
|
+
def prepare_parsed_frame frame
|
71
|
+
force_body_encoding frame
|
72
|
+
end
|
73
|
+
|
74
|
+
if RUBY_VERSION >= "1.9"
|
75
|
+
# Encodes the given string to 'UTF-8'
|
76
|
+
# @note No-op for Ruby 1.8.x
|
77
|
+
# @param [String] s
|
78
|
+
# @return [String]
|
79
|
+
def encode_header(s)
|
80
|
+
s.encoding.name == 'UTF-8' ? s : s.encode('UTF-8')
|
81
|
+
end
|
82
|
+
# Forces the encoding of the given string to 'UTF-8'
|
83
|
+
# @note No-op for Ruby 1.8.x
|
84
|
+
# @param [String] s
|
85
|
+
# @return [String]
|
86
|
+
def force_header_encoding(s); s.tap { s.force_encoding('UTF-8') }; end
|
87
|
+
# Forces the encoding of the given frame's body to match its charset.
|
88
|
+
# @note No-op for Ruby 1.8.x
|
89
|
+
# @param [OnStomp::Components::Frame] f
|
90
|
+
# @return [OnStomp::Components::Frame]
|
91
|
+
def force_body_encoding f
|
92
|
+
type, subtype, charset = f.content_type
|
93
|
+
charset ||= (type == 'text') ? 'UTF-8' : 'ASCII-8BIT'
|
94
|
+
f.body.force_encoding(charset)
|
95
|
+
f
|
96
|
+
end
|
97
|
+
# Set an appropriate +content-type+ header with +charset+ parameter for
|
98
|
+
# frames with a text body
|
99
|
+
# @note No-op for Ruby 1.8.x
|
100
|
+
# @param [OnStomp::Components::Frame] f
|
101
|
+
# @return [OnStomp::Components::Frame]
|
102
|
+
def make_ct f
|
103
|
+
return f if f.body.nil?
|
104
|
+
t, st = f.content_type
|
105
|
+
enc = f.body.encoding.name
|
106
|
+
if enc != 'ASCII-8BIT'
|
107
|
+
f[:'content-type'] = "#{t||'text'}/#{st||'plain'};charset=#{enc}"
|
108
|
+
end
|
109
|
+
f
|
110
|
+
end
|
111
|
+
else
|
112
|
+
# Encodes the given string to 'UTF-8'
|
113
|
+
# @note No-op for Ruby 1.8.x
|
114
|
+
# @param [String] s
|
115
|
+
# @return [String]
|
116
|
+
def encode_header(s); s; end
|
117
|
+
# Forces the encoding of the given string to 'UTF-8'
|
118
|
+
# @note No-op for Ruby 1.8.x
|
119
|
+
# @param [String] s
|
120
|
+
# @return [String]
|
121
|
+
def force_header_encoding(s); s; end
|
122
|
+
# Forces the encoding of the given frame's body to match its charset.
|
123
|
+
# @note No-op for Ruby 1.8.x
|
124
|
+
# @param [OnStomp::Components::Frame] f
|
125
|
+
# @return [OnStomp::Components::Frame]
|
126
|
+
def force_body_encoding(f); f; end
|
127
|
+
# Set an appropriate +content-type+ header with +charset+ parameter for
|
128
|
+
# frames with a text body
|
129
|
+
# @note No-op for Ruby 1.8.x
|
130
|
+
# @param [OnStomp::Components::Frame] f
|
131
|
+
# @return [OnStomp::Components::Frame]
|
132
|
+
def make_ct(f); f; end
|
133
|
+
end
|
134
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
# Namespace for {OnStomp::Components::Frame frame} serializers / parsers.
|
4
|
+
module OnStomp::Connections::Serializers
|
5
|
+
end
|
6
|
+
|
7
|
+
require 'onstomp/connections/serializers/stomp_1'
|
8
|
+
require 'onstomp/connections/serializers/stomp_1_0'
|
9
|
+
require 'onstomp/connections/serializers/stomp_1_1'
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
# Connection behavior common to both STOMP 1.0 and STOMP 1.1 connections
|
4
|
+
module OnStomp::Connections::Stomp_1
|
5
|
+
# Creates a CONNECT frame
|
6
|
+
# @return [OnStomp::Components::Frame] CONNECT frame
|
7
|
+
def connect_frame *h
|
8
|
+
create_frame 'CONNECT', h
|
9
|
+
end
|
10
|
+
|
11
|
+
# Creates a SEND frame
|
12
|
+
# @return [OnStomp::Components::Frame] SEND frame
|
13
|
+
def send_frame d, b, h
|
14
|
+
create_frame 'SEND', [h, {:destination => d}], b
|
15
|
+
end
|
16
|
+
|
17
|
+
# Creates a BEGIN frame
|
18
|
+
# @return [OnStomp::Components::Frame] BEGIN frame
|
19
|
+
def begin_frame tx, h
|
20
|
+
create_transaction_frame 'BEGIN', tx, h
|
21
|
+
end
|
22
|
+
|
23
|
+
# Creates a COMMIT frame
|
24
|
+
# @return [OnStomp::Components::Frame] COMMIT frame
|
25
|
+
def commit_frame tx, h
|
26
|
+
create_transaction_frame 'COMMIT', tx, h
|
27
|
+
end
|
28
|
+
|
29
|
+
# Creates an ABORT frame
|
30
|
+
# @return [OnStomp::Components::Frame] ABORT frame
|
31
|
+
def abort_frame tx, h
|
32
|
+
create_transaction_frame 'ABORT', tx, h
|
33
|
+
end
|
34
|
+
|
35
|
+
# Creates a DISCONNECT frame
|
36
|
+
# @return [OnStomp::Components::Frame] DISCONNECT frame
|
37
|
+
def disconnect_frame h
|
38
|
+
create_frame 'DISCONNECT', [h]
|
39
|
+
end
|
40
|
+
|
41
|
+
# Creates a SUBSCRIBE frame
|
42
|
+
# @return [OnStomp::Components::Frame] SUBSCRIBE frame
|
43
|
+
def subscribe_frame d, h
|
44
|
+
create_frame 'SUBSCRIBE', [{:id => OnStomp.next_serial}, h, {:destination => d}]
|
45
|
+
end
|
46
|
+
|
47
|
+
# Creates an UNSUBSCRIBE frame
|
48
|
+
# @return [OnStomp::Components::Frame] UNSUBSCRIBE frame
|
49
|
+
def unsubscribe_frame f, h
|
50
|
+
id = f.is_a?(OnStomp::Components::Frame) ? f[:id] : f
|
51
|
+
create_frame('UNSUBSCRIBE', [{:id => id}, h]).tap do |f|
|
52
|
+
raise ArgumentError, 'subscription ID could not be determined' unless f.header?(:id)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
def create_transaction_frame command, tx, headers
|
58
|
+
create_frame command, [headers, {:transaction => tx}]
|
59
|
+
end
|
60
|
+
|
61
|
+
def create_frame command, layered_headers, body=nil
|
62
|
+
headers = layered_headers.inject({}) do |final, h|
|
63
|
+
h = OnStomp.keys_to_sym(h).delete_if { |k,v| final.key?(k) && (v.nil? || v.empty?) }
|
64
|
+
final.merge!(h)
|
65
|
+
final
|
66
|
+
end
|
67
|
+
OnStomp::Components::Frame.new(command, headers, body)
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
# A STOMP 1.0 specific connection
|
4
|
+
class OnStomp::Connections::Stomp_1_0 < OnStomp::Connections::Base
|
5
|
+
include OnStomp::Connections::Stomp_1
|
6
|
+
# The serializer that will convert {OnStomp::Components::Frame frames} into
|
7
|
+
# raw bytes and will convert raw bytes into {OnStomp::Components::Frame frames}
|
8
|
+
# @return [OnStomp::Connections::Serializers::Stomp_1_0]
|
9
|
+
attr_reader :serializer
|
10
|
+
|
11
|
+
# Calls {OnStomp::Connections::Base#initialize} and creates a STOMP 1.0
|
12
|
+
# serializer
|
13
|
+
def initialize socket, client
|
14
|
+
super
|
15
|
+
@serializer = OnStomp::Connections::Serializers::Stomp_1_0.new
|
16
|
+
end
|
17
|
+
|
18
|
+
# Creates an ACK frame
|
19
|
+
# @return [OnStomp::Components::Frame] ACK frame
|
20
|
+
def ack_frame *args
|
21
|
+
headers = args.last.is_a?(Hash) ? args.pop : {}
|
22
|
+
m_id = args.shift
|
23
|
+
m_id = m_id[:'message-id'] if m_id.is_a?(OnStomp::Components::Frame)
|
24
|
+
create_frame('ACK', [{:'message-id' => m_id}, headers]).tap do |f|
|
25
|
+
raise ArgumentError, 'no message-id to ACK' unless f.header?(:'message-id')
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
# A STOMP 1.1 specific connection
|
4
|
+
class OnStomp::Connections::Stomp_1_1 < OnStomp::Connections::Base
|
5
|
+
include OnStomp::Connections::Stomp_1
|
6
|
+
include OnStomp::Connections::Heartbeating
|
7
|
+
# The serializer that will convert {OnStomp::Components::Frame frames} into
|
8
|
+
# raw bytes and will convert raw bytes into {OnStomp::Components::Frame frames}
|
9
|
+
# @return [OnStomp::Connections::Serializers::Stomp_1_1]
|
10
|
+
attr_reader :serializer
|
11
|
+
|
12
|
+
# Calls {OnStomp::Connections::Base#initialize} and creates a STOMP 1.0
|
13
|
+
# serializer
|
14
|
+
def initialize socket, client
|
15
|
+
super
|
16
|
+
@serializer = OnStomp::Connections::Serializers::Stomp_1_1.new
|
17
|
+
end
|
18
|
+
|
19
|
+
# Calls {OnStomp::Connections::Base#configure} then configures heartbeating
|
20
|
+
# parameters.
|
21
|
+
# @param [OnStomp::Components::Frame] connected
|
22
|
+
# @param [{Symbol => Proc}] con_cbs
|
23
|
+
def configure connected, con_cbs
|
24
|
+
super
|
25
|
+
configure_heartbeating client.heartbeats, connected.heart_beat
|
26
|
+
end
|
27
|
+
|
28
|
+
# Returns true if {OnStomp::Connections::Base#connected?} is true and
|
29
|
+
# we have a {#pulse?}
|
30
|
+
def connected?
|
31
|
+
super && pulse?
|
32
|
+
end
|
33
|
+
|
34
|
+
# Creates an ACK frame
|
35
|
+
# @return [OnStomp::Components::Frame] ACK frame
|
36
|
+
def ack_frame *args
|
37
|
+
create_ack_or_nack 'ACK', args
|
38
|
+
end
|
39
|
+
|
40
|
+
# Creates an NACK frame
|
41
|
+
# @return [OnStomp::Components::Frame] NACK frame
|
42
|
+
def nack_frame *args
|
43
|
+
create_ack_or_nack 'NACK', args
|
44
|
+
end
|
45
|
+
|
46
|
+
# Creates a heartbeat frame (serialized as a single "\n" character)
|
47
|
+
# @return [OnStomp::Components::Frame] heartbeat frame
|
48
|
+
def heartbeat_frame
|
49
|
+
OnStomp::Components::Frame.new
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
def create_ack_or_nack(command, args)
|
54
|
+
headers = args.last.is_a?(Hash) ? args.pop : {}
|
55
|
+
m_id = args.shift
|
56
|
+
sub_id = args.shift
|
57
|
+
if m_id.is_a?(OnStomp::Components::Frame)
|
58
|
+
sub_id = m_id[:subscription]
|
59
|
+
m_id = m_id[:'message-id']
|
60
|
+
end
|
61
|
+
create_frame(command, [{:'message-id' => m_id, :subscription => sub_id }, headers]).tap do |f|
|
62
|
+
raise ::ArgumentError, 'missing message-id or subscription headers' unless f.headers?(:'message-id', :subscription)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
# Namespace for protocol specific connections used to communicate with
|
4
|
+
# STOMP brokers.
|
5
|
+
module OnStomp::Connections
|
6
|
+
# Default SSL options to use when establishing an SSL connection.
|
7
|
+
DEFAULT_SSL_OPTIONS = {
|
8
|
+
:verify_mode => OpenSSL::SSL::VERIFY_PEER |
|
9
|
+
OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT,
|
10
|
+
:ca_file => nil,
|
11
|
+
:ca_path => nil,
|
12
|
+
:cert => nil,
|
13
|
+
:key => nil,
|
14
|
+
:post_connection_check => true
|
15
|
+
}
|
16
|
+
|
17
|
+
# Returns a list of supported protocol versions
|
18
|
+
# @return [Array<String>]
|
19
|
+
def self.supported
|
20
|
+
PROTOCOL_VERSIONS.keys.sort
|
21
|
+
end
|
22
|
+
|
23
|
+
# Filters a list of supplied versions to only those
|
24
|
+
# that are supported. Results are in the same order as they are found
|
25
|
+
# in {.supported}. If none of the supplied versions are supported, an
|
26
|
+
# empty list is returned.
|
27
|
+
# @param [Array<String>] vers versions to filter
|
28
|
+
# @return [Array<String>]
|
29
|
+
def self.select_supported vers
|
30
|
+
vers = Array(vers)
|
31
|
+
supported.select { |v| vers.include? v }
|
32
|
+
end
|
33
|
+
|
34
|
+
# Creates an initial {OnStomp::Connections::Stomp_1_0 connection} to
|
35
|
+
# the client's broker uri, performs the CONNECT/CONNECTED frame exchange,
|
36
|
+
# and returns a {OnStomp::Connections::Base connection} suitable for the
|
37
|
+
# negotiated STOMP protocol version.
|
38
|
+
# @param [OnStomp::Client] client
|
39
|
+
# @param [{#to_sym => #to_s}] u_head user specified headers for CONNECT frame
|
40
|
+
# @param [{#to_sym => #to_s}] c_head client specified headers for CONNECT frame
|
41
|
+
# @param [{Symbol => Proc}] con_cbs event callbacks to install on the final
|
42
|
+
# connection
|
43
|
+
# @return [OnStomp::Connections::Base] instance of Base subclass suited for
|
44
|
+
# negotiated protocol version
|
45
|
+
# @raise [OnStomp::OnStompError] if negotiating the connection raises an
|
46
|
+
# such an error.
|
47
|
+
def self.connect client, u_head, c_head, con_cbs
|
48
|
+
init_con = create_connection('1.0', nil, client)
|
49
|
+
ver, connected = init_con.connect client, u_head, c_head
|
50
|
+
begin
|
51
|
+
negotiate_connection(ver, init_con, client).tap do |final_con|
|
52
|
+
final_con.configure connected, con_cbs
|
53
|
+
end
|
54
|
+
rescue OnStomp::OnStompError
|
55
|
+
# Perform a blocking close.
|
56
|
+
init_con.close true
|
57
|
+
raise
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
def self.negotiate_connection vers, con, client
|
63
|
+
supports_protocol?(vers,con) ? con :
|
64
|
+
create_connection(vers, con.socket, client)
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.supports_protocol? ver, con
|
68
|
+
con.is_a? PROTOCOL_VERSIONS[ver]
|
69
|
+
end
|
70
|
+
|
71
|
+
def self.create_connection ver, sock, client
|
72
|
+
unless sock
|
73
|
+
meth = client.ssl ? :ssl :
|
74
|
+
client.uri.respond_to?(:onstomp_socket_type) ?
|
75
|
+
client.uri.onstomp_socket_type : :tcp
|
76
|
+
sock = __send__(:"create_socket_#{meth}", client)
|
77
|
+
end
|
78
|
+
PROTOCOL_VERSIONS[ver].new sock, client
|
79
|
+
end
|
80
|
+
|
81
|
+
def self.create_socket_tcp client
|
82
|
+
TCPSocket.new(client.uri.host || 'localhost', client.uri.port)
|
83
|
+
end
|
84
|
+
|
85
|
+
def self.create_socket_ssl client
|
86
|
+
uri = client.uri
|
87
|
+
host = uri.host || 'localhost'
|
88
|
+
ssl_opts = client.ssl.is_a?(Hash) ? client.ssl : {}
|
89
|
+
ssl_opts = DEFAULT_SSL_OPTIONS.merge(ssl_opts)
|
90
|
+
context = OpenSSL::SSL::SSLContext.new
|
91
|
+
post_check = ssl_opts.delete(:post_connection_check)
|
92
|
+
post_check_host = (post_check == true) ? host : post_check
|
93
|
+
DEFAULT_SSL_OPTIONS.keys.each do |k|
|
94
|
+
context.__send__(:"#{k}=", ssl_opts[k]) if ssl_opts.key?(k)
|
95
|
+
end
|
96
|
+
tcp_sock = create_socket_tcp(client)
|
97
|
+
socket = OpenSSL::SSL::SSLSocket.new(tcp_sock, context)
|
98
|
+
socket.sync_close = true
|
99
|
+
socket.connect
|
100
|
+
socket.post_connection_check(post_check_host) if post_check_host
|
101
|
+
socket
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
require 'onstomp/connections/serializers'
|
106
|
+
require 'onstomp/connections/base'
|
107
|
+
require 'onstomp/connections/stomp_1'
|
108
|
+
require 'onstomp/connections/heartbeating'
|
109
|
+
require 'onstomp/connections/stomp_1_0'
|
110
|
+
require 'onstomp/connections/stomp_1_1'
|
111
|
+
|
112
|
+
module OnStomp::Connections
|
113
|
+
# A mapping of protocol versions to the connection classes that support
|
114
|
+
# them.
|
115
|
+
PROTOCOL_VERSIONS = {
|
116
|
+
'1.0' => OnStomp::Connections::Stomp_1_0,
|
117
|
+
'1.1' => OnStomp::Connections::Stomp_1_1
|
118
|
+
}
|
119
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
# Module for configurable attributes specific to {OnStomp::Client client} objects.
|
4
|
+
module OnStomp::Interfaces::ClientConfigurable
|
5
|
+
# Includes {OnStomp::Interfaces::UriConfigurable} into +base+ and
|
6
|
+
# extends {OnStomp::Interfaces::ClientConfigurable::ClassMethods}
|
7
|
+
# @param [Module] base
|
8
|
+
def self.included(base)
|
9
|
+
base.__send__ :include, OnStomp::Interfaces::UriConfigurable
|
10
|
+
base.extend ClassMethods
|
11
|
+
end
|
12
|
+
|
13
|
+
# Provides attribute methods for {OnStomp::Client client} objects.
|
14
|
+
module ClassMethods
|
15
|
+
# Creates a readable and writeable attribute with the given name that
|
16
|
+
# defaults to the {OnStomp::Connections.supported supported} protocol
|
17
|
+
# versions and is {OnStomp::Connections.select_supported filtered} to
|
18
|
+
# those versions when assigned. Corresponds to which protocol versions
|
19
|
+
# should be used for a given client's connection.
|
20
|
+
# @param [Symbol] nm name of attribute
|
21
|
+
def attr_configurable_protocols nm
|
22
|
+
attr_configurable_arr(nm, :default => OnStomp::Connections.supported) do |vers|
|
23
|
+
OnStomp::Connections.select_supported(vers).tap do |valid|
|
24
|
+
raise OnStomp::UnsupportedProtocolVersionError, vers.inspect if valid.empty?
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Creates a readable and writeable attribute with the given name that
|
30
|
+
# defaults to [0, 0] and is mapped to a pair of non-negative integers
|
31
|
+
# when assigned. Corresponds to what heartbeating strategy should be used
|
32
|
+
# for a given client's connection where [0, 0] indicates no heartbeating
|
33
|
+
# should be performed.
|
34
|
+
# @note This attribute is only useful with STOMP 1.1 connections.
|
35
|
+
# @param [Symbol] nm name of attribute
|
36
|
+
def attr_configurable_client_beats nm
|
37
|
+
attr_configurable_arr(nm, :default => [0,0]) do |val|
|
38
|
+
val.map { |b| bi = b.to_i; bi < 0 ? 0 : bi }
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Creates a readable and writeable attribute with the given name that
|
43
|
+
# defaults to the {OnStomp::Components::ThreadedProcessor} and if set
|
44
|
+
# to nil will instead use {OnStomp::Components::NilProcessor}. Corresponds
|
45
|
+
# the the class to use when create new processor instances when a client
|
46
|
+
# is connected.
|
47
|
+
# @param [Symbol] nm name of attribute
|
48
|
+
def attr_configurable_processor nm
|
49
|
+
attr_configurable_class(nm,
|
50
|
+
:default => OnStomp::Components::ThreadedProcessor) do |pr|
|
51
|
+
pr || OnStomp::Components::NilProcessor
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,168 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
# Mixin for {OnStomp::Client client} events
|
4
|
+
# There are a few special event methods that will be passed on
|
5
|
+
# to the client's connection, they are:
|
6
|
+
# * +on_connection_established+ => {OnStomp::Interfaces::ConnectionEvents#on_established}
|
7
|
+
# * +on_connection_died+ => {OnStomp::Interfaces::ConnectionEvents#on_died}
|
8
|
+
# * +on_connection_terminated+ => {OnStomp::Interfaces::ConnectionEvents#on_terminated}
|
9
|
+
# * +on_connection_closed+ => {OnStomp::Interfaces::ConnectionEvents#on_closed}
|
10
|
+
module OnStomp::Interfaces::ClientEvents
|
11
|
+
include OnStomp::Interfaces::EventManager
|
12
|
+
|
13
|
+
# @group Client Frame Event Bindings
|
14
|
+
|
15
|
+
# Binds a callback to be invoked when an ACK frame is transmitted
|
16
|
+
# @yield [frame, client] callback invoked when event is triggered
|
17
|
+
# @yieldparam [OnStomp::Components::Frame] frame
|
18
|
+
# @yieldparam [OnStomp::Client] client
|
19
|
+
create_event_methods :ack, :before, :on
|
20
|
+
# Binds a callback to be invoked when a NACK frame is transmitted
|
21
|
+
# @yield [frame, client] callback invoked when event is triggered
|
22
|
+
# @yieldparam [OnStomp::Components::Frame] frame
|
23
|
+
# @yieldparam [OnStomp::Client] client
|
24
|
+
create_event_methods :nack, :before, :on
|
25
|
+
# Binds a callback to be invoked when a BEGIN frame is transmitted
|
26
|
+
# @yield [frame, client] callback invoked when event is triggered
|
27
|
+
# @yieldparam [OnStomp::Components::Frame] frame
|
28
|
+
# @yieldparam [OnStomp::Client] client
|
29
|
+
create_event_methods :begin, :before, :on
|
30
|
+
# Binds a callback to be invoked when an ABORT frame is transmitted
|
31
|
+
# @yield [frame, client] callback invoked when event is triggered
|
32
|
+
# @yieldparam [OnStomp::Components::Frame] frame
|
33
|
+
# @yieldparam [OnStomp::Client] client
|
34
|
+
create_event_methods :abort, :before, :on
|
35
|
+
# Binds a callback to be invoked when a COMMIT frame is transmitted
|
36
|
+
# @yield [frame, client] callback invoked when event is triggered
|
37
|
+
# @yieldparam [OnStomp::Components::Frame] frame
|
38
|
+
# @yieldparam [OnStomp::Client] client
|
39
|
+
create_event_methods :commit, :before, :on
|
40
|
+
# Binds a callback to be invoked when a SEND frame is transmitted
|
41
|
+
# @yield [frame, client] callback invoked when event is triggered
|
42
|
+
# @yieldparam [OnStomp::Components::Frame] frame
|
43
|
+
# @yieldparam [OnStomp::Client] client
|
44
|
+
create_event_methods :send, :before, :on
|
45
|
+
# Binds a callback to be invoked when a SUBSCRIBE frame is transmitted
|
46
|
+
# @yield [frame, client] callback invoked when event is triggered
|
47
|
+
# @yieldparam [OnStomp::Components::Frame] frame
|
48
|
+
# @yieldparam [OnStomp::Client] client
|
49
|
+
create_event_methods :subscribe, :before, :on
|
50
|
+
# Binds a callback to be invoked when an UNSUBSCRIBE frame is transmitted
|
51
|
+
# @yield [frame, client] callback invoked when event is triggered
|
52
|
+
# @yieldparam [OnStomp::Components::Frame] frame
|
53
|
+
# @yieldparam [OnStomp::Client] client
|
54
|
+
create_event_methods :unsubscribe, :before, :on
|
55
|
+
# Binds a callback to be invoked when a DISCONNECT frame is transmitted
|
56
|
+
# @yield [frame, client] callback invoked when event is triggered
|
57
|
+
# @yieldparam [OnStomp::Components::Frame] frame
|
58
|
+
# @yieldparam [OnStomp::Client] client
|
59
|
+
create_event_methods :disconnect, :before, :on
|
60
|
+
# Binds a callback to be invoked when a client heartbeat is transmitted
|
61
|
+
# @yield [frame, client] callback invoked when event is triggered
|
62
|
+
# @yieldparam [OnStomp::Components::Frame] frame
|
63
|
+
# @yieldparam [OnStomp::Client] client
|
64
|
+
create_event_methods :client_beat, :before, :on
|
65
|
+
|
66
|
+
# @group Broker Frame Event Bindings
|
67
|
+
|
68
|
+
# Binds a callback to be invoked when an ERROR frame is received
|
69
|
+
# @yield [frame, client] callback invoked when event is triggered
|
70
|
+
# @yieldparam [OnStomp::Components::Frame] frame
|
71
|
+
# @yieldparam [OnStomp::Client] client
|
72
|
+
create_event_methods :error, :before, :on
|
73
|
+
# Binds a callback to be invoked when a MESSAGE frame is received
|
74
|
+
# @yield [frame, client] callback invoked when event is triggered
|
75
|
+
# @yieldparam [OnStomp::Components::Frame] frame
|
76
|
+
# @yieldparam [OnStomp::Client] client
|
77
|
+
create_event_methods :message, :before, :on
|
78
|
+
# Binds a callback to be invoked when a RECEIPT frame is received
|
79
|
+
# @yield [frame, client] callback invoked when event is triggered
|
80
|
+
# @yieldparam [OnStomp::Components::Frame] frame
|
81
|
+
# @yieldparam [OnStomp::Client] client
|
82
|
+
create_event_methods :receipt, :before, :on
|
83
|
+
# Binds a callback to be invoked when a broker heartbeat is received
|
84
|
+
# @yield [frame, client] callback invoked when event is triggered
|
85
|
+
# @yieldparam [OnStomp::Components::Frame] frame
|
86
|
+
# @yieldparam [OnStomp::Client] client
|
87
|
+
create_event_methods :broker_beat, :before, :on
|
88
|
+
|
89
|
+
# @group Frame Exchange Event Bindings
|
90
|
+
|
91
|
+
# Binds a callback to be invoked when any frame is transmitted
|
92
|
+
# @yield [frame, client] callback invoked when event is triggered
|
93
|
+
# @yieldparam [OnStomp::Components::Frame] frame
|
94
|
+
# @yieldparam [OnStomp::Client] client
|
95
|
+
create_event_methods :transmitting, :before, :after
|
96
|
+
# Binds a callback to be invoked when any frame is received
|
97
|
+
# @yield [frame, client] callback invoked when event is triggered
|
98
|
+
# @yieldparam [OnStomp::Components::Frame] frame
|
99
|
+
# @yieldparam [OnStomp::Client] client
|
100
|
+
create_event_methods :receiving, :before, :after
|
101
|
+
|
102
|
+
# @endgroup
|
103
|
+
|
104
|
+
# Helpers for setting up connection events through a client
|
105
|
+
[:established, :terminated, :died, :closed].each do |ev|
|
106
|
+
module_eval <<-EOS
|
107
|
+
def on_connection_#{ev}(&cb)
|
108
|
+
if connection
|
109
|
+
connection.on_#{ev}(&cb)
|
110
|
+
else
|
111
|
+
pending_connection_events[:on_#{ev}] << cb
|
112
|
+
end
|
113
|
+
end
|
114
|
+
EOS
|
115
|
+
end
|
116
|
+
|
117
|
+
# Returns a hash of event bindings that should be set on a
|
118
|
+
# {OnStomp::Connections::Base connection}, but were set on the client
|
119
|
+
# because the connection does not exist yet.
|
120
|
+
# @return [{Symbol => Array<Proc>}]
|
121
|
+
def pending_connection_events
|
122
|
+
@pending_connection_events ||= Hash.new { |h,k| h[k] = [] }
|
123
|
+
end
|
124
|
+
|
125
|
+
# Triggers an event named by the supplied frame, prefixed with the supplied
|
126
|
+
# prefix. If the supplied frame is a 'heart-beat', origin will be used to
|
127
|
+
# dispatch appropriate heart-beat event (client_beat or broker_beat)
|
128
|
+
# @param [OnStomp::Components::Frame] f the frame trigger this event
|
129
|
+
# @param [:on, :before, etc] pref the prefix for the event name
|
130
|
+
# @param [:client, :broker] origin
|
131
|
+
def trigger_frame_event f, pref, origin
|
132
|
+
e = f.command ? :"#{pref}_#{f.command.downcase}" :
|
133
|
+
:"#{pref}_#{origin}_beat"
|
134
|
+
trigger_event e, f, self
|
135
|
+
end
|
136
|
+
|
137
|
+
# Triggers the :before_receiving event and the
|
138
|
+
# +before+ prefixed frame specific event (eg: +:before_error+).
|
139
|
+
# @param [OnStomp::Components::Frame] f
|
140
|
+
def trigger_before_receiving f
|
141
|
+
trigger_event :before_receiving, f, self
|
142
|
+
trigger_frame_event f, :before, :broker
|
143
|
+
end
|
144
|
+
|
145
|
+
# Triggers the :after_receiving event and the
|
146
|
+
# +on+ prefixed frame specific event (eg: +:on_message+)
|
147
|
+
# @param [OnStomp::Components::Frame] f
|
148
|
+
def trigger_after_receiving f
|
149
|
+
trigger_event :after_receiving, f, self
|
150
|
+
trigger_frame_event f, :on, :broker
|
151
|
+
end
|
152
|
+
|
153
|
+
# Triggers the :before_transmitting event and the
|
154
|
+
# +before+ prefixed frame specific event (eg: +:before_disconnect+).
|
155
|
+
# @param [OnStomp::Components::Frame] f
|
156
|
+
def trigger_before_transmitting f
|
157
|
+
trigger_event :before_transmitting, f, self
|
158
|
+
trigger_frame_event f, :before, :client
|
159
|
+
end
|
160
|
+
|
161
|
+
# Triggers the :after_transmitting event and the
|
162
|
+
# +on+ prefixed frame specific event (eg: +:on_send+).
|
163
|
+
# @param [OnStomp::Components::Frame] f
|
164
|
+
def trigger_after_transmitting f
|
165
|
+
trigger_event :after_transmitting, f, self
|
166
|
+
trigger_frame_event f, :on, :client
|
167
|
+
end
|
168
|
+
end
|