onstomp 1.0.0pre1
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/.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,62 @@
|
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
# Mixin for connection events
|
|
4
|
+
module OnStomp::Interfaces::ConnectionEvents
|
|
5
|
+
include OnStomp::Interfaces::EventManager
|
|
6
|
+
|
|
7
|
+
# @group Connection State Events
|
|
8
|
+
|
|
9
|
+
# Binds a callback to be invoked when a connection has been fully
|
|
10
|
+
# established between broker and client.
|
|
11
|
+
# @yield [client, connection] callback invoked when event is triggered
|
|
12
|
+
# @yieldparam [OnStomp::Client] client
|
|
13
|
+
# @yieldparam [OnStomp::Connections::Base] connection that triggered
|
|
14
|
+
# the event (in general the same as +client.connection+)
|
|
15
|
+
create_event_methods :established, :on
|
|
16
|
+
# Binds a callback to be invoked when a connection has been died due to
|
|
17
|
+
# insufficient data transfer.
|
|
18
|
+
# @note Only applies to STOMP 1.1 connections with heartbeating enabled.
|
|
19
|
+
# @yield [client, connection] callback invoked when event is triggered
|
|
20
|
+
# @yieldparam [OnStomp::Client] client
|
|
21
|
+
# @yieldparam [OnStomp::Connections::Base] connection that triggered
|
|
22
|
+
# the event (in general the same as +client.connection+)
|
|
23
|
+
create_event_methods :died, :on
|
|
24
|
+
# Binds a callback to be invoked when a connection has been terminated
|
|
25
|
+
# (eg: closed unexpectedly due to an exception)
|
|
26
|
+
# @yield [client, connection] callback invoked when event is triggered
|
|
27
|
+
# @yieldparam [OnStomp::Client] client
|
|
28
|
+
# @yieldparam [OnStomp::Connections::Base] connection that triggered
|
|
29
|
+
# the event (in general the same as +client.connection+)
|
|
30
|
+
create_event_methods :terminated, :on
|
|
31
|
+
# Binds a callback to be invoked when a connection has been closed, either
|
|
32
|
+
# through a graceful disconnect or unexpectedly.
|
|
33
|
+
# @note If connection is closed unexpectedly, {#on_died} is triggered first,
|
|
34
|
+
# followed by this event.
|
|
35
|
+
# @yield [client, connection] callback invoked when event is triggered
|
|
36
|
+
# @yieldparam [OnStomp::Client] client
|
|
37
|
+
# @yieldparam [OnStomp::Connections::Base] connection that triggered
|
|
38
|
+
# the event (in general the same as +client.connection+)
|
|
39
|
+
create_event_methods :closed, :on
|
|
40
|
+
|
|
41
|
+
# @endgroup
|
|
42
|
+
|
|
43
|
+
# Triggers a connection specific event.
|
|
44
|
+
# @param [Symbol] event name
|
|
45
|
+
def trigger_connection_event event
|
|
46
|
+
trigger_event :"on_#{event}", self.client, self
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Takes a hash of event bindings a {OnStomp::Client client} has stored
|
|
50
|
+
# and binds them to this connection, then triggers +on_established+.
|
|
51
|
+
# This allows users to add callbacks for
|
|
52
|
+
# connection events before the connection exist and have said callbacks
|
|
53
|
+
# installed once the connection is created.
|
|
54
|
+
# @param [{Symbol => Array<Proc>}] callbacks to install, keyed by event name
|
|
55
|
+
# @see OnStomp::Interfaces::ClientEvents#pending_connection_events
|
|
56
|
+
def install_bindings_from_client ev_hash
|
|
57
|
+
ev_hash.each do |ev, cbs|
|
|
58
|
+
cbs.each { |cb| bind_event(ev, cb) }
|
|
59
|
+
end
|
|
60
|
+
trigger_connection_event :established
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
# Mixin for event management.
|
|
4
|
+
module OnStomp::Interfaces::EventManager
|
|
5
|
+
# Extends base with {OnStomp::Interfaces::EventManager::ClassMethods}
|
|
6
|
+
def self.included(base)
|
|
7
|
+
base.extend ClassMethods
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
# Binds a +Proc+ to be invoked when the given +event_name+ is triggered.
|
|
11
|
+
# @param [Symbol] event_name
|
|
12
|
+
# @param [Proc] cb_proc
|
|
13
|
+
# @return [self]
|
|
14
|
+
def bind_event(event_name, cb_proc)
|
|
15
|
+
event_callbacks[event_name] << cb_proc
|
|
16
|
+
self
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Returns a hash of event names mapped to arrays of proc callbacks.
|
|
20
|
+
# @return [{Symbol => Array<Proc>}]
|
|
21
|
+
def event_callbacks
|
|
22
|
+
@event_callbacks ||= Hash.new { |h, k| h[k] = [] }
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Triggers an event by the given name, passing along any additional
|
|
26
|
+
# +args+ as parameters to the callback
|
|
27
|
+
# @param [Symbol] event_name event to trigger
|
|
28
|
+
# @param [Object, Object, ...] args
|
|
29
|
+
def trigger_event(event_name, *args)
|
|
30
|
+
event_callbacks[event_name].each { |cb| cb.call(*args) }
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Mixin to allow includers to define custom event methods
|
|
34
|
+
module ClassMethods
|
|
35
|
+
# Creates a convenience method for binding callbacks to the given
|
|
36
|
+
# event name.
|
|
37
|
+
# @param [Symbol] name
|
|
38
|
+
# @example
|
|
39
|
+
# class ExampleClass
|
|
40
|
+
# include OnStomp::Interfaces::EventManager
|
|
41
|
+
#
|
|
42
|
+
# create_event_method :do_event
|
|
43
|
+
# end
|
|
44
|
+
#
|
|
45
|
+
# example_obj.do_event { |arg1, arg2| ... }
|
|
46
|
+
def create_event_method name
|
|
47
|
+
module_eval "def #{name}(&block); bind_event(:#{name}, block); end"
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Creates convenience methods for binding callbacks to the given
|
|
51
|
+
# event name with a set of prefixes.
|
|
52
|
+
# @param [Symbol] name
|
|
53
|
+
# @param [Symbol, Symbol, ...] prefixes (eg: :on, :before, :after)
|
|
54
|
+
# @example
|
|
55
|
+
# class ExampleClass
|
|
56
|
+
# include OnStomp::Interfaces::EventManager
|
|
57
|
+
#
|
|
58
|
+
# create_event_methods :some_event, :before, :during, :after
|
|
59
|
+
# end
|
|
60
|
+
#
|
|
61
|
+
# example_obj.before_some_event { |arg| ... }
|
|
62
|
+
# example_obj.after_some_event { |arg| ... }
|
|
63
|
+
# example_obj.during_some_event { |arg| ... }
|
|
64
|
+
def create_event_methods name, *prefixes
|
|
65
|
+
prefixes << :on if prefixes.empty?
|
|
66
|
+
prefixes.each { |pre| create_event_method :"#{pre}_#{name}" }
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
# Mixin for {OnStomp::Client clients} to provide methods that create
|
|
4
|
+
# and transmit STOMP {OnStomp::Components::Frame frames}.
|
|
5
|
+
module OnStomp::Interfaces::FrameMethods
|
|
6
|
+
# Transmits a SEND frame generated by the client's connection
|
|
7
|
+
# @param [String] dest destination for the frame
|
|
8
|
+
# @param [String] body body of the frame
|
|
9
|
+
# @param [{#to_sym => #to_s}] headers additional headers to include in
|
|
10
|
+
# the frame
|
|
11
|
+
# @return [OnStomp::Components::Frame] SEND frame
|
|
12
|
+
# @yield [receipt] block to invoke when a RECEIPT frame is received for the
|
|
13
|
+
# transmitted SEND frame
|
|
14
|
+
# @yieldparam [OnStomp::Components::Frame] receipt RECEIPT for the frame
|
|
15
|
+
# @raise OnStomp::UnsupportedCommandError if the connection does not support
|
|
16
|
+
# SEND frames
|
|
17
|
+
def send dest, body, headers={}, &cb
|
|
18
|
+
transmit connection.send_frame(dest, body, headers), :receipt => cb
|
|
19
|
+
end
|
|
20
|
+
alias :puts :send
|
|
21
|
+
|
|
22
|
+
# Transmits a SUBSCRIBE frame generated by the client's connection. Depending
|
|
23
|
+
# upon the connection, a subscription can be set to various MESSAGE
|
|
24
|
+
# acknowledgement modes by setting the +:ack+ header.
|
|
25
|
+
# STOMP 1.0 and STOMP 1.1 connections support:
|
|
26
|
+
# * :ack => 'auto'
|
|
27
|
+
# The broker assumes that MESSAGE frames received through the
|
|
28
|
+
# subscription have been properly received, the client should NOT attempt
|
|
29
|
+
# to ACK (or NACK) any of the messages.
|
|
30
|
+
# * :ack => 'client'
|
|
31
|
+
# The broker assumes that MESSAGE frames should be acknowledged by the
|
|
32
|
+
# client through the use of ACK frames.
|
|
33
|
+
# STOMP 1.1 connections support:
|
|
34
|
+
# * :ack => 'client-individual'
|
|
35
|
+
# Upon receiving an ACK frame for a MESSAGE frame, some brokers will
|
|
36
|
+
# mark the MESSAGE frame and all those sent to the client before it
|
|
37
|
+
# as acknowledged. This mode indicates that each MESSAGE frame must
|
|
38
|
+
# be acknowledged by its own ACK frame for the broker can assume the
|
|
39
|
+
# MESSAGE frame has been received by the client.
|
|
40
|
+
# @param [String] dest destination for the frame
|
|
41
|
+
# @param [{#to_sym => #to_s}] headers additional headers to include in
|
|
42
|
+
# the frame
|
|
43
|
+
# @return [OnStomp::Components::Frame] SUBSCRIBE frame
|
|
44
|
+
# @yield [message] block to invoke for every MESSAGE frame received on the
|
|
45
|
+
# subscription
|
|
46
|
+
# @yieldparam [OnStomp::Components::Frame] message MESSAGE frame received on
|
|
47
|
+
# the subscription
|
|
48
|
+
# @raise OnStomp::UnsupportedCommandError if the connection does not support
|
|
49
|
+
# SUBSCRIBE frames
|
|
50
|
+
# @see #unsubscribe
|
|
51
|
+
# @see #ack
|
|
52
|
+
# @see #nack
|
|
53
|
+
def subscribe dest, headers={}, &cb
|
|
54
|
+
transmit connection.subscribe_frame(dest, headers), :subscribe => cb
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Transmits an UNSUBSCRIBE frame generated by the client's connection.
|
|
58
|
+
# @overload unsubscribe(subscribe_frame, headers={})
|
|
59
|
+
# Generates an UNSUBSCRIBE frame to match the given SUBSCRIBE frame
|
|
60
|
+
# @param [OnStomp::Components::Frame] subscribe_frame
|
|
61
|
+
# @param [{#to_sym => #to_s}] headers optional headers to include in
|
|
62
|
+
# the UNSUBSCRIBE frame
|
|
63
|
+
# @overload unsubscribe(id, headers={})
|
|
64
|
+
# Generates an UNSUBSCRIBE frame with the given id
|
|
65
|
+
# @param [String] id
|
|
66
|
+
# @param [{#to_sym => #to_s}] headers optional headers to include in
|
|
67
|
+
# the UNSUBSCRIBE frame
|
|
68
|
+
# @return [OnStomp::Components::Frame] UNSUBSCRIBE frame
|
|
69
|
+
# @raise OnStomp::UnsupportedCommandError if the connection does not support
|
|
70
|
+
# UNSUBSCRIBE frames
|
|
71
|
+
# @see #subscribe
|
|
72
|
+
def unsubscribe frame_or_id, headers={}
|
|
73
|
+
transmit connection.unsubscribe_frame(frame_or_id, headers)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Transmits a BEGIN frame generated by the client's connection to start
|
|
77
|
+
# a transaction.
|
|
78
|
+
# @param [String] tx_id identifier for the transaction
|
|
79
|
+
# @param [{#to_sym => #to_s}] headers additional headers to include in
|
|
80
|
+
# the frame
|
|
81
|
+
# @return [OnStomp::Components::Frame] BEGIN frame
|
|
82
|
+
# @raise OnStomp::UnsupportedCommandError if the connection does not support
|
|
83
|
+
# BEGIN frames
|
|
84
|
+
# @see #abort
|
|
85
|
+
# @see #commit
|
|
86
|
+
def begin tx_id, headers={}
|
|
87
|
+
transmit connection.begin_frame(tx_id, headers)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Transmits an ABORT frame generated by the client's connection to rollback
|
|
91
|
+
# a transaction.
|
|
92
|
+
# @param [String] tx_id identifier for the transaction
|
|
93
|
+
# @param [{#to_sym => #to_s}] headers additional headers to include in
|
|
94
|
+
# the frame
|
|
95
|
+
# @return [OnStomp::Components::Frame] ABORT frame
|
|
96
|
+
# @raise OnStomp::UnsupportedCommandError if the connection does not support
|
|
97
|
+
# ABORT frames
|
|
98
|
+
# @see #begin
|
|
99
|
+
# @see #commit
|
|
100
|
+
def abort tx_id, headers={}
|
|
101
|
+
transmit connection.abort_frame(tx_id, headers)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Transmits a COMMIT frame generated by the client's connection to complete
|
|
105
|
+
# a transaction.
|
|
106
|
+
# @param [String] tx_id identifier for the transaction
|
|
107
|
+
# @param [{#to_sym => #to_s}] headers additional headers to include in
|
|
108
|
+
# the frame
|
|
109
|
+
# @return [OnStomp::Components::Frame] COMMIT frame
|
|
110
|
+
# @raise OnStomp::UnsupportedCommandError if the connection does not support
|
|
111
|
+
# COMMIT frames
|
|
112
|
+
# @see #abort
|
|
113
|
+
# @see #begin
|
|
114
|
+
def commit tx_id, headers={}
|
|
115
|
+
transmit connection.commit_frame(tx_id, headers)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# Transmits a DISCONNECT frame generated by the client's connection to end
|
|
119
|
+
# the STOMP session.
|
|
120
|
+
# @param [{#to_sym => #to_s}] headers additional headers to include in
|
|
121
|
+
# the frame
|
|
122
|
+
# @return [OnStomp::Components::Frame] DISCONNECT frame
|
|
123
|
+
# @raise OnStomp::UnsupportedCommandError if the connection does not support
|
|
124
|
+
# DISCONNECT frames
|
|
125
|
+
def disconnect headers={}
|
|
126
|
+
transmit connection.disconnect_frame headers
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Transmits an ACK frame generated by the client's connection.
|
|
130
|
+
# @overload ack(message_frame, headers={})
|
|
131
|
+
# @note Users should use this form whenever possible as it will work
|
|
132
|
+
# with STOMP 1.0 and 1.1 connections.
|
|
133
|
+
# @param [OnStomp::Components::Frame] message_frame the MESSAGE frame to
|
|
134
|
+
# acknowledge.
|
|
135
|
+
# @param [{#to_sym => #to_s}] headers additional headers to include in
|
|
136
|
+
# the frame
|
|
137
|
+
# @overload ack(message_id, headers={})
|
|
138
|
+
# @note This form will raise an +ArgumentError+ with STOMP 1.1 connections
|
|
139
|
+
# as a subscription ID is also required to ACK a received MESSAGE.
|
|
140
|
+
# @param [String] message_id +message-id+ header of MESSAGE frame to
|
|
141
|
+
# acknowledge.
|
|
142
|
+
# @param [{#to_sym => #to_s}] headers additional headers to include in
|
|
143
|
+
# the frame
|
|
144
|
+
# @overload ack(message_id, subscription_id, heders={})
|
|
145
|
+
# @note This form should be used with STOMP 1.1 connections when it is
|
|
146
|
+
# not possible to provide the actual MESSAGE frame.
|
|
147
|
+
# @param [String] message_id +message-id+ header of MESSAGE frame to
|
|
148
|
+
# acknowledge.
|
|
149
|
+
# @param [String] subscription_id +subscription+ header of MESSAGE frame to
|
|
150
|
+
# acknowledge.
|
|
151
|
+
# @param [{#to_sym => #to_s}] headers additional headers to include in
|
|
152
|
+
# the frame
|
|
153
|
+
# @return [OnStomp::Components::Frame] ACK frame
|
|
154
|
+
# @raise OnStomp::UnsupportedCommandError if the connection does not support
|
|
155
|
+
# ACK frames
|
|
156
|
+
# @see #nack
|
|
157
|
+
def ack *args
|
|
158
|
+
transmit connection.ack_frame(*args)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# Transmits a NACK frame generated by the client's connection.
|
|
162
|
+
# @overload nack(message_frame, headers={})
|
|
163
|
+
# Generates a NACK frame for the given MESSAGE frame.
|
|
164
|
+
# @param [OnStomp::Components::Frame] message_frame the MESSAGE frame to
|
|
165
|
+
# un-acknowledge.
|
|
166
|
+
# @param [{#to_sym => #to_s}] headers additional headers to include in
|
|
167
|
+
# the frame
|
|
168
|
+
# @overload nack(message_id, subscription_id, heders={})
|
|
169
|
+
# @param [String] message_id +message-id+ header of MESSAGE frame to
|
|
170
|
+
# un-acknowledge.
|
|
171
|
+
# @param [String] subscription_id +subscription+ header of MESSAGE frame to
|
|
172
|
+
# un-acknowledge.
|
|
173
|
+
# @param [{#to_sym => #to_s}] headers additional headers to include in
|
|
174
|
+
# the frame
|
|
175
|
+
# @return [OnStomp::Components::Frame] NACK frame
|
|
176
|
+
# @raise OnStomp::UnsupportedCommandError if the connection does not support
|
|
177
|
+
# NACK frames
|
|
178
|
+
# @see #ack
|
|
179
|
+
def nack *args
|
|
180
|
+
transmit connection.nack_frame(*args)
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
# Transmits a client heartbeat frame generated by the client's connection.
|
|
184
|
+
# @return [OnStomp::Components::Frame] heartbeat frame
|
|
185
|
+
# @raise OnStomp::UnsupportedCommandError if the connection does not support
|
|
186
|
+
# heartbeat frames
|
|
187
|
+
def beat
|
|
188
|
+
transmit connection.heartbeat_frame
|
|
189
|
+
end
|
|
190
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
# Mixin for {OnStomp::Client clients} to provide receipt management
|
|
4
|
+
module OnStomp::Interfaces::ReceiptManager
|
|
5
|
+
private
|
|
6
|
+
def configure_receipt_management
|
|
7
|
+
@receipt_monitor = Monitor.new
|
|
8
|
+
@receipt_backs = {}
|
|
9
|
+
before_disconnect do |d, con|
|
|
10
|
+
@receipt_to_close = d[:receipt] if d[:receipt]
|
|
11
|
+
end
|
|
12
|
+
on_receipt do |r, con|
|
|
13
|
+
dispatch_receipt r
|
|
14
|
+
close if r[:'receipt-id'] == @receipt_to_close
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def add_receipt f, cb
|
|
19
|
+
f[:receipt] = OnStomp.next_serial unless f.header?(:receipt)
|
|
20
|
+
@receipt_monitor.synchronize { @receipt_backs[f[:receipt]] = cb }
|
|
21
|
+
self
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def clear_receipts
|
|
25
|
+
@receipt_monitor.synchronize { @receipt_backs.clear }
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def dispatch_receipt receipt
|
|
29
|
+
cb = @receipt_monitor.synchronize { @receipt_backs.delete(receipt[:'receipt-id']) }
|
|
30
|
+
cb && cb.call(receipt)
|
|
31
|
+
self
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
# Mixin for {OnStomp::Client clients} to provide receipt management
|
|
4
|
+
module OnStomp::Interfaces::SubscriptionManager
|
|
5
|
+
# Returns an array of {OnStomp::Components::Subscription} objects for all
|
|
6
|
+
# currently active subscriptions.
|
|
7
|
+
# @return [Array<OnStomp::Components::Subscription>]
|
|
8
|
+
def subscriptions
|
|
9
|
+
@subcription_mon.synchronize { @subscriptions.values }
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
private
|
|
13
|
+
def configure_subscription_management
|
|
14
|
+
@subcription_mon = Monitor.new
|
|
15
|
+
@subscriptions = {}
|
|
16
|
+
on_message { |m, c| dispatch_subscription m }
|
|
17
|
+
on_unsubscribe { |u, c| remove_subscription u[:id] }
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def add_subscription f, cb
|
|
21
|
+
s_id = f[:id]
|
|
22
|
+
dest = f[:destination]
|
|
23
|
+
@subcription_mon.synchronize do
|
|
24
|
+
@subscriptions[s_id] = OnStomp::Components::Subscription.new(f, cb)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def remove_subscription sub_id
|
|
29
|
+
@subcription_mon.synchronize do
|
|
30
|
+
@subscriptions.delete sub_id
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def clear_subscriptions
|
|
35
|
+
@subcription_mon.synchronize { @subscriptions.clear }
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def dispatch_subscription m
|
|
39
|
+
if m.header? :subscription
|
|
40
|
+
sub = @subcription_mon.synchronize { @subscriptions[m[:subscription]] }
|
|
41
|
+
sub && sub.call(m)
|
|
42
|
+
else
|
|
43
|
+
@subcription_mon.synchronize do
|
|
44
|
+
@subscriptions.values.select { |sub| sub.include? m }
|
|
45
|
+
end.each { |sub| sub.call m }
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
# Module for configurable attributes
|
|
4
|
+
module OnStomp::Interfaces::UriConfigurable
|
|
5
|
+
# Extends +base+ with {OnStomp::Interfaces::UriConfigurable::ClassMethods}
|
|
6
|
+
def self.included(base)
|
|
7
|
+
base.extend ClassMethods
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
private
|
|
11
|
+
def configure_configurable hash_opts
|
|
12
|
+
config = OnStomp.keys_to_sym(CGI.parse(uri.query || '')).
|
|
13
|
+
merge(OnStomp.keys_to_sym(hash_opts))
|
|
14
|
+
self.class.config_attributes.each do |attr_name, attr_conf|
|
|
15
|
+
attr_val = config.key?(attr_name) ? config[attr_name] :
|
|
16
|
+
attr_conf.key?(:uri_attr) && uri.__send__(attr_conf[:uri_attr]) ||
|
|
17
|
+
attr_conf[:default]
|
|
18
|
+
__send__(:"#{attr_name}=", attr_val)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Provides attribute methods that can be configured by URI attributes
|
|
23
|
+
# or query parameters.
|
|
24
|
+
module ClassMethods
|
|
25
|
+
# Creates a group readable and writeable attributes that can be set
|
|
26
|
+
# by a URI query parameter sharing the same name, a property of a URI or
|
|
27
|
+
# a default value. The value of this attribute will be transformed by
|
|
28
|
+
# invoking the given block, if one has been provided.
|
|
29
|
+
def attr_configurable *args, &block
|
|
30
|
+
opts = args.last.is_a?(Hash) ? args.pop : {}
|
|
31
|
+
args.each do |attr_name|
|
|
32
|
+
__init_config_attribute__ attr_name, opts
|
|
33
|
+
attr_reader attr_name
|
|
34
|
+
define_method :"#{attr_name}=" do |v|
|
|
35
|
+
instance_variable_set(:"@#{attr_name}", (block ? block.call(v) : v))
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Creates a group readable and writeable attributes that can be set
|
|
41
|
+
# by a URI query parameter sharing the same name, a property of a URI or
|
|
42
|
+
# a default value. The value of this attribute will be transformed by
|
|
43
|
+
# invoking the given block, if one has been provided. If the attributes
|
|
44
|
+
# created by this method are assigned an +Array+, only the first element
|
|
45
|
+
# will be used as their value.
|
|
46
|
+
def attr_configurable_single *args, &block
|
|
47
|
+
trans = __attr_configurable_wrap__ lambda { |v| v.is_a?(Array) ? v.first : v }, block
|
|
48
|
+
attr_configurable(*args, &trans)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Creates a group readable and writeable attributes that can be set
|
|
52
|
+
# by a URI query parameter sharing the same name, a property of a URI or
|
|
53
|
+
# a default value. The value of this attribute will be transformed by
|
|
54
|
+
# invoking the given block, if one has been provided. The attributes
|
|
55
|
+
# created by this method will be treated as though they were created
|
|
56
|
+
# with {#attr_configurable_single} and will also be converted into Strings.
|
|
57
|
+
def attr_configurable_str *args, &block
|
|
58
|
+
trans = __attr_configurable_wrap__ lambda { |v| v.to_s }, block
|
|
59
|
+
attr_configurable_single(*args, &trans)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Creates a group readable and writeable attributes that can be set
|
|
63
|
+
# by a URI query parameter sharing the same name, a property of a URI or
|
|
64
|
+
# a default value. The value of this attribute will be transformed by
|
|
65
|
+
# invoking the given block, if one has been provided. If the attributes
|
|
66
|
+
# created by this method are assigned a value that is not an +Array+, the
|
|
67
|
+
# value will be wrapped in an array.
|
|
68
|
+
def attr_configurable_arr *args, &block
|
|
69
|
+
trans = __attr_configurable_wrap__ lambda { |v| Array(v) }, block
|
|
70
|
+
attr_configurable(*args, &trans)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Creates a group readable and writeable attributes that can be set
|
|
74
|
+
# by a URI query parameter sharing the same name, a property of a URI or
|
|
75
|
+
# a default value. The value of this attribute will be transformed by
|
|
76
|
+
# invoking the given block, if one has been provided. The attributes
|
|
77
|
+
# created by this method will be treated as though they were created
|
|
78
|
+
# with {#attr_configurable_single} and will also be converted into Class
|
|
79
|
+
# or Module objects.
|
|
80
|
+
def attr_configurable_class *args, &block
|
|
81
|
+
trans = __attr_configurable_wrap__ lambda { |v| OnStomp.constantize(v) }, block
|
|
82
|
+
attr_configurable_single(*args, &trans)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
private
|
|
86
|
+
def __attr_configurable_wrap__(trans, block)
|
|
87
|
+
if block
|
|
88
|
+
lambda { |v| block.call(trans.call(v)) }
|
|
89
|
+
else
|
|
90
|
+
trans
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def __init_config_attribute__(name, opts)
|
|
95
|
+
unless respond_to?(:config_attributes)
|
|
96
|
+
class << self
|
|
97
|
+
def config_attributes
|
|
98
|
+
@config_attributes ||= {}
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
config_attributes[name] ||= {}
|
|
103
|
+
config_attributes[name].merge!(opts)
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
# Namespace for interface mixins
|
|
4
|
+
module OnStomp::Interfaces
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
require 'onstomp/interfaces/uri_configurable'
|
|
8
|
+
require 'onstomp/interfaces/client_configurable'
|
|
9
|
+
require 'onstomp/interfaces/frame_methods'
|
|
10
|
+
require 'onstomp/interfaces/event_manager'
|
|
11
|
+
require 'onstomp/interfaces/connection_events'
|
|
12
|
+
require 'onstomp/interfaces/client_events'
|
|
13
|
+
require 'onstomp/interfaces/receipt_manager'
|
|
14
|
+
require 'onstomp/interfaces/subscription_manager'
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
# Primary namespace for the +onstomp+ gem
|
|
4
|
+
module OnStomp
|
|
5
|
+
# Major / API version
|
|
6
|
+
MAJOR = 1
|
|
7
|
+
# Minor / feature version
|
|
8
|
+
MINOR = 0
|
|
9
|
+
# Patch version
|
|
10
|
+
PATCH = 0
|
|
11
|
+
# Complete version
|
|
12
|
+
VERSION = "#{MAJOR}.#{MINOR}.#{PATCH}pre1"
|
|
13
|
+
end
|
data/lib/onstomp.rb
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
# Copyright 2011 Ian D. Eccles
|
|
4
|
+
#
|
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
# you may not use this file except in compliance with the License.
|
|
7
|
+
# You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
# See the License for the specific language governing permissions and
|
|
15
|
+
# limitations under the License.
|
|
16
|
+
|
|
17
|
+
# For extensions to URI.parse for Stomp schemes.
|
|
18
|
+
require 'uri'
|
|
19
|
+
# Primarily for CGI.parse
|
|
20
|
+
require 'cgi'
|
|
21
|
+
# Sockets are fairly important in all of this.
|
|
22
|
+
require 'socket'
|
|
23
|
+
# As is openssl
|
|
24
|
+
require 'openssl'
|
|
25
|
+
# For IO#ready?
|
|
26
|
+
require 'io/wait'
|
|
27
|
+
# The socket helpers use this to delegate to the real sockets
|
|
28
|
+
require 'delegate'
|
|
29
|
+
# Threading and Mutex support
|
|
30
|
+
require 'thread'
|
|
31
|
+
# Monitor support (prevent recursive dead locking)
|
|
32
|
+
require 'monitor'
|
|
33
|
+
|
|
34
|
+
# Primary namespace for the +onstomp+ gem
|
|
35
|
+
module OnStomp
|
|
36
|
+
# A common base class for errors raised by the OnStomp gem
|
|
37
|
+
# @abstract
|
|
38
|
+
class OnStompError < StandardError; end
|
|
39
|
+
|
|
40
|
+
# Low level error raised when the broker transmits data that violates
|
|
41
|
+
# the Stomp protocol specification.
|
|
42
|
+
# @abstract
|
|
43
|
+
class FatalProtocolError < OnStompError; end
|
|
44
|
+
|
|
45
|
+
# Raised when an invalid character is encountered in a header
|
|
46
|
+
class InvalidHeaderCharacterError < FatalProtocolError; end
|
|
47
|
+
|
|
48
|
+
# Raised when an invalid escape sequence is encountered in a header name or value
|
|
49
|
+
class InvalidHeaderEscapeSequenceError < FatalProtocolError; end
|
|
50
|
+
|
|
51
|
+
# Raised when a malformed header is encountered. For example, if a header
|
|
52
|
+
# line does not contain ':'
|
|
53
|
+
class MalformedHeaderError < FatalProtocolError; end
|
|
54
|
+
|
|
55
|
+
# Raised when a malformed frame is encountered on the stream. For example,
|
|
56
|
+
# if a frame is not properly terminated with the {OnStomp::FrameIO::FRAME_TERMINATOR}
|
|
57
|
+
# character.
|
|
58
|
+
class MalformedFrameError < FatalProtocolError; end
|
|
59
|
+
|
|
60
|
+
# An error that is raised as a result of a misconfiguration of the client
|
|
61
|
+
# connection
|
|
62
|
+
# @abstract
|
|
63
|
+
class FatalConnectionError < OnStompError; end
|
|
64
|
+
|
|
65
|
+
# Raised when a connection has been configured with an unsupported protocol
|
|
66
|
+
# version. This can be due to end user misconfiguration, or due to improper
|
|
67
|
+
# protocol negotiation with the message broker.
|
|
68
|
+
class UnsupportedProtocolVersionError < FatalConnectionError; end
|
|
69
|
+
|
|
70
|
+
# Raised when an attempt to connect to the broker results in an unexpected
|
|
71
|
+
# exchange.
|
|
72
|
+
class ConnectFailedError < FatalConnectionError; end
|
|
73
|
+
|
|
74
|
+
# Raised if the command issued is not supported by the protocol version
|
|
75
|
+
# negotiated between the client and broker.
|
|
76
|
+
class UnsupportedCommandError < OnStompError; end
|
|
77
|
+
|
|
78
|
+
# An error that is raised as a result frames being generated on
|
|
79
|
+
# a transaction while it is in an invalid state.
|
|
80
|
+
# @abstract
|
|
81
|
+
class TransactionError < OnStompError; end
|
|
82
|
+
|
|
83
|
+
# Raised by ThreadedReceiver to stop the receiving thread.
|
|
84
|
+
class StopReceiver < StandardError; end
|
|
85
|
+
|
|
86
|
+
class << self
|
|
87
|
+
# Creates a new connection and immediately connects it to the broker.
|
|
88
|
+
# @see #initialize
|
|
89
|
+
def connect(uri, options={})
|
|
90
|
+
conx = OnStomp::Client.new(uri, options)
|
|
91
|
+
conx.connect
|
|
92
|
+
conx
|
|
93
|
+
end
|
|
94
|
+
alias :open :connect
|
|
95
|
+
|
|
96
|
+
# Duplicates an existing hash while transforming its keys to symbols.
|
|
97
|
+
# The keys must implement the +to_sym+ method, otherwise an exception will
|
|
98
|
+
# be raised. This method is used internally to convert hashes keyed with
|
|
99
|
+
# Strings.
|
|
100
|
+
#
|
|
101
|
+
# @param [{Object => Object}] hsh The hash to convert. It's keys must respond to +to_sym+.
|
|
102
|
+
# @return [{Symbol => Object}]
|
|
103
|
+
# @example
|
|
104
|
+
# hash = { '10' => nil, 'key2' => [3, 5, 8, 13, 21], :other => :value }
|
|
105
|
+
# OnStomp.keys_to_sym(hash) #=> { :'10' => nil, :key2 => [3, 5, 8, 13, 21], :other => :value }
|
|
106
|
+
# hash #=> { '10' => nil, 'key2' => [3, 5, 8, 13, 21], :other => :value }
|
|
107
|
+
def keys_to_sym(hsh)
|
|
108
|
+
hsh.inject({}) do |new_hash, (k,v)|
|
|
109
|
+
new_hash[k.to_sym] = v
|
|
110
|
+
new_hash
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Generates the next serial number in a thread-safe manner. This method
|
|
115
|
+
# merely initializes an instance variable to 0 if it has not been set,
|
|
116
|
+
# then increments this value and returns its string representation.
|
|
117
|
+
def next_serial(prefix=nil)
|
|
118
|
+
Thread.exclusive do
|
|
119
|
+
@next_serial_sequence ||= 0
|
|
120
|
+
@next_serial_sequence += 1
|
|
121
|
+
@next_serial_sequence.to_s
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Converts a string to the Ruby constant it names. If the +klass+ parameter
|
|
126
|
+
# is a kind of +Module+, this method will return +klass+ directly.
|
|
127
|
+
# @param [String,Module] klass
|
|
128
|
+
# @return [Module]
|
|
129
|
+
# @example
|
|
130
|
+
# OnStomp.constantize('OnStomp::Frame') #=> OnStomp::Frame
|
|
131
|
+
# OnStomp.constantize('This::Constant::DoesNotExist) #=> raises NameError
|
|
132
|
+
# OnStomp.constantize(Symbol) #=> Symbol
|
|
133
|
+
def constantize(klass)
|
|
134
|
+
return klass if klass.is_a?(Module) || klass.nil? || klass.respond_to?(:new)
|
|
135
|
+
klass.to_s.split('::').inject(Object) do |const, named|
|
|
136
|
+
next const if named.empty?
|
|
137
|
+
const.const_defined?(named) ? const.const_get(named) :
|
|
138
|
+
const.const_missing(named)
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
require 'onstomp/version'
|
|
144
|
+
require 'onstomp/interfaces'
|
|
145
|
+
require 'onstomp/components'
|
|
146
|
+
require 'onstomp/connections'
|
|
147
|
+
require 'onstomp/client'
|