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,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'
|