stomper 1.0.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +5 -0
- data/{spec/spec.opts → .rspec} +0 -2
- data/Gemfile +4 -0
- data/LICENSE +201 -201
- data/README.md +130 -0
- data/Rakefile +5 -0
- data/examples/basic.rb +38 -0
- data/examples/events.rb +54 -0
- data/features/acking_messages.feature +147 -0
- data/features/disconnecting.feature +12 -0
- data/features/establish_connection.feature +44 -0
- data/features/protocol_version_negotiation.feature +61 -0
- data/features/receipts.feature +72 -0
- data/features/scopes.feature +32 -0
- data/features/secure_connections.feature +38 -0
- data/features/send_and_message.feature +28 -0
- data/features/steps/acking_messages_steps.rb +39 -0
- data/features/steps/disconnecting_steps.rb +8 -0
- data/features/steps/establish_connection_steps.rb +74 -0
- data/features/steps/frame_transmission_steps.rb +35 -0
- data/features/steps/protocol_version_negotiation_steps.rb +15 -0
- data/features/steps/receipts_steps.rb +79 -0
- data/features/steps/scopes_steps.rb +52 -0
- data/features/steps/secure_connections_steps.rb +41 -0
- data/features/steps/send_and_message_steps.rb +35 -0
- data/features/steps/subscribing_steps.rb +36 -0
- data/features/steps/threaded_receiver_steps.rb +8 -0
- data/features/steps/transactions_steps.rb +0 -0
- data/features/subscribing.feature +151 -0
- data/features/support/env.rb +11 -0
- data/features/support/header_helpers.rb +12 -0
- data/features/support/ssl/README +6 -0
- data/features/support/ssl/broker_cert.csr +17 -0
- data/features/support/ssl/broker_cert.pem +72 -0
- data/features/support/ssl/broker_key.pem +27 -0
- data/features/support/ssl/client_cert.csr +17 -0
- data/features/support/ssl/client_cert.pem +72 -0
- data/features/support/ssl/client_key.pem +27 -0
- data/features/support/ssl/demoCA/cacert.pem +17 -0
- data/features/support/ssl/demoCA/index.txt +2 -0
- data/features/support/ssl/demoCA/index.txt.attr +1 -0
- data/features/support/ssl/demoCA/index.txt.attr.old +1 -0
- data/features/support/ssl/demoCA/index.txt.old +1 -0
- data/features/support/ssl/demoCA/newcerts/01.pem +72 -0
- data/features/support/ssl/demoCA/newcerts/02.pem +72 -0
- data/features/support/ssl/demoCA/private/cakey.pem +17 -0
- data/features/support/ssl/demoCA/serial +1 -0
- data/features/support/ssl/demoCA/serial.old +1 -0
- data/features/support/test_stomp_server.rb +150 -0
- data/features/threaded_receiver.feature +11 -0
- data/features/transactions.feature +66 -0
- data/lib/stomper.rb +30 -20
- data/lib/stomper/connection.rb +442 -102
- data/lib/stomper/errors.rb +59 -0
- data/lib/stomper/extensions.rb +10 -0
- data/lib/stomper/extensions/common.rb +258 -0
- data/lib/stomper/extensions/events.rb +213 -0
- data/lib/stomper/extensions/heartbeat.rb +101 -0
- data/lib/stomper/extensions/scoping.rb +56 -0
- data/lib/stomper/frame.rb +54 -0
- data/lib/stomper/frame_serializer.rb +217 -0
- data/lib/stomper/headers.rb +15 -0
- data/lib/stomper/receipt_manager.rb +36 -0
- data/lib/stomper/receivers.rb +7 -0
- data/lib/stomper/receivers/threaded.rb +71 -0
- data/lib/stomper/scopes.rb +9 -0
- data/lib/stomper/scopes/header_scope.rb +49 -0
- data/lib/stomper/scopes/receipt_scope.rb +44 -0
- data/lib/stomper/scopes/transaction_scope.rb +109 -0
- data/lib/stomper/sockets.rb +66 -28
- data/lib/stomper/subscription_manager.rb +79 -0
- data/lib/stomper/support.rb +68 -0
- data/lib/stomper/support/1.8/frame_serializer.rb +53 -0
- data/lib/stomper/support/1.8/headers.rb +183 -0
- data/lib/stomper/support/1.9/frame_serializer.rb +64 -0
- data/lib/stomper/support/1.9/headers.rb +172 -0
- data/lib/stomper/support/ruby.rb +13 -0
- data/lib/stomper/uris.rb +49 -0
- data/lib/stomper/version.rb +7 -0
- data/spec/spec_helper.rb +13 -9
- data/spec/stomper/connection_spec.rb +712 -0
- data/spec/stomper/extensions/common_spec.rb +187 -0
- data/spec/stomper/extensions/events_spec.rb +78 -0
- data/spec/stomper/extensions/heartbeat_spec.rb +103 -0
- data/spec/stomper/extensions/scoping_spec.rb +21 -0
- data/spec/stomper/frame_serializer_1.8_spec.rb +318 -0
- data/spec/stomper/frame_serializer_spec.rb +316 -0
- data/spec/stomper/frame_spec.rb +36 -0
- data/spec/stomper/headers_spec.rb +224 -0
- data/spec/stomper/receipt_manager_spec.rb +91 -0
- data/spec/stomper/receivers/threaded_spec.rb +116 -0
- data/spec/stomper/scopes/header_scope_spec.rb +42 -0
- data/spec/stomper/scopes/receipt_scope_spec.rb +51 -0
- data/spec/stomper/scopes/transaction_scope_spec.rb +183 -0
- data/spec/stomper/sockets_spec.rb +113 -0
- data/spec/stomper/subscription_manager_spec.rb +107 -0
- data/spec/stomper/support_spec.rb +69 -0
- data/spec/stomper/uris_spec.rb +54 -0
- data/spec/stomper_spec.rb +9 -0
- data/spec/support/custom_argument_matchers.rb +57 -0
- data/spec/support/existential_frame_matchers.rb +19 -0
- data/spec/support/frame_header_matchers.rb +10 -0
- data/stomper.gemspec +30 -0
- metadata +272 -97
- data/AUTHORS +0 -21
- data/CHANGELOG +0 -20
- data/README.rdoc +0 -120
- data/lib/stomper/client.rb +0 -34
- data/lib/stomper/frame_reader.rb +0 -73
- data/lib/stomper/frame_writer.rb +0 -21
- data/lib/stomper/frames.rb +0 -39
- data/lib/stomper/frames/abort.rb +0 -10
- data/lib/stomper/frames/ack.rb +0 -25
- data/lib/stomper/frames/begin.rb +0 -11
- data/lib/stomper/frames/client_frame.rb +0 -89
- data/lib/stomper/frames/commit.rb +0 -10
- data/lib/stomper/frames/connect.rb +0 -10
- data/lib/stomper/frames/connected.rb +0 -30
- data/lib/stomper/frames/disconnect.rb +0 -10
- data/lib/stomper/frames/error.rb +0 -21
- data/lib/stomper/frames/message.rb +0 -48
- data/lib/stomper/frames/receipt.rb +0 -19
- data/lib/stomper/frames/send.rb +0 -10
- data/lib/stomper/frames/server_frame.rb +0 -38
- data/lib/stomper/frames/subscribe.rb +0 -42
- data/lib/stomper/frames/unsubscribe.rb +0 -19
- data/lib/stomper/open_uri_interface.rb +0 -41
- data/lib/stomper/receipt_handlers.rb +0 -23
- data/lib/stomper/receiptor.rb +0 -38
- data/lib/stomper/subscriber.rb +0 -76
- data/lib/stomper/subscription.rb +0 -128
- data/lib/stomper/subscriptions.rb +0 -95
- data/lib/stomper/threaded_receiver.rb +0 -59
- data/lib/stomper/transaction.rb +0 -185
- data/lib/stomper/transactor.rb +0 -50
- data/lib/stomper/uri.rb +0 -55
- data/spec/client_spec.rb +0 -29
- data/spec/connection_spec.rb +0 -22
- data/spec/frame_reader_spec.rb +0 -37
- data/spec/frame_writer_spec.rb +0 -27
- data/spec/frames/client_frame_spec.rb +0 -66
- data/spec/frames/indirect_frame_spec.rb +0 -45
- data/spec/frames/server_frame_spec.rb +0 -85
- data/spec/open_uri_interface_spec.rb +0 -132
- data/spec/receiptor_spec.rb +0 -35
- data/spec/shared_connection_examples.rb +0 -79
- data/spec/subscriber_spec.rb +0 -77
- data/spec/subscription_spec.rb +0 -157
- data/spec/subscriptions_spec.rb +0 -145
- data/spec/threaded_receiver_spec.rb +0 -33
- data/spec/transaction_spec.rb +0 -139
- data/spec/transactor_spec.rb +0 -46
@@ -0,0 +1,36 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
# Manages receipt handling for a {Stomper::Connection}
|
4
|
+
class Stomper::ReceiptManager
|
5
|
+
# Creates a new receipt handler for the supplied {Stomper::Connection connection}
|
6
|
+
# @param [Stomper::Connection] connection
|
7
|
+
def initialize(connection)
|
8
|
+
@mon = ::Monitor.new
|
9
|
+
@callbacks = {}
|
10
|
+
connection.before_disconnect do |d, con|
|
11
|
+
@close_on = d[:receipt] if d[:receipt]
|
12
|
+
end
|
13
|
+
connection.on_receipt do |r, con|
|
14
|
+
dispatch(r)
|
15
|
+
connection.close if r[:'receipt-id'] == @close_on
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# Adds a callback handler for a RECEIPT frame that matches the supplied
|
20
|
+
# receipt ID.
|
21
|
+
# @param [String] r_id ID of the receipt to match
|
22
|
+
# @param [Proc] callback Proc to invoke when a matching RECEIPT frame is
|
23
|
+
# received from the broker.
|
24
|
+
# @return [self]
|
25
|
+
def add(r_id, callback)
|
26
|
+
@mon.synchronize { @callbacks[r_id] = callback }
|
27
|
+
self
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
def dispatch(receipt)
|
32
|
+
cb = @mon.synchronize { @callbacks.delete(receipt[:'receipt-id']) }
|
33
|
+
cb && cb.call(receipt)
|
34
|
+
self
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
# Basic threaded receiver
|
4
|
+
class Stomper::Receivers::Threaded
|
5
|
+
# Returns true if the receiver is currently running, false otherwise.
|
6
|
+
# If the polling thread is terminated due to a raised exception, this
|
7
|
+
# attribute will be false.
|
8
|
+
# @return [true,false]
|
9
|
+
attr_reader :running
|
10
|
+
alias :running? :running
|
11
|
+
|
12
|
+
# Creates a new threaded receiver for the supplied {Stomper::Connection}.
|
13
|
+
# Invoking {#start} on this receiver will create a new thread that will
|
14
|
+
# continually call {Stomper::Connection#receive receive} on the
|
15
|
+
# {Stomper::Connection connection}. Stopping this receiver with {#stop}
|
16
|
+
# will terminate the thread.
|
17
|
+
# @param [Stomper::Connection] connection
|
18
|
+
def initialize(connection)
|
19
|
+
@connection = connection
|
20
|
+
@running = false
|
21
|
+
@run_mutex = ::Mutex.new
|
22
|
+
@run_thread = nil
|
23
|
+
@raised_while_running = nil
|
24
|
+
end
|
25
|
+
|
26
|
+
# Starts the receiver by creating a new thread to continually poll the
|
27
|
+
# {Stomper::Connection connection} for new Stomp frames. If an error is
|
28
|
+
# raised while calling {Stomper::Connection#receive}, the polling thread
|
29
|
+
# will terminate, and {#running?} will return false.
|
30
|
+
# @return [self]
|
31
|
+
def start
|
32
|
+
is_starting = @run_mutex.synchronize { @running = true unless @running }
|
33
|
+
if is_starting
|
34
|
+
@run_thread = Thread.new do
|
35
|
+
while @running
|
36
|
+
begin
|
37
|
+
@running = false if @connection.receive.nil?
|
38
|
+
rescue Exception => ex
|
39
|
+
@running = false
|
40
|
+
raise ex
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
self
|
46
|
+
end
|
47
|
+
|
48
|
+
# Stops the receiver by shutting down the polling thread. If an error was
|
49
|
+
# raised within the thread, this method will generally re-raise the error.
|
50
|
+
# The one exception to this behavior is if the error raised was an instance
|
51
|
+
# of +IOError+ and a call to {Stomper::Connection#connected?} returns false,
|
52
|
+
# in which case the error is ignored. The reason for this is that performing
|
53
|
+
# a read operation on a closed stream will raise an +IOError+. It is likely
|
54
|
+
# that when shutting down a connection and its receiver, the polling thread
|
55
|
+
# may be blocked on reading from the stream and raise such an error.
|
56
|
+
# @return [self]
|
57
|
+
# @raise [Exception]
|
58
|
+
def stop
|
59
|
+
stopped = @run_mutex.synchronize { @run_thread.nil? }
|
60
|
+
unless stopped
|
61
|
+
@running = false
|
62
|
+
begin
|
63
|
+
@run_thread.join
|
64
|
+
rescue IOError
|
65
|
+
raise if @connection.connected?
|
66
|
+
end
|
67
|
+
@run_thread = nil
|
68
|
+
end
|
69
|
+
self
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
# A "connection scope" that provides much of the same interface as
|
4
|
+
# {Stomper::Connection}, but automatically applies header name/value pairs
|
5
|
+
# to all frames generated on the scope.
|
6
|
+
class ::Stomper::Scopes::HeaderScope
|
7
|
+
include ::Stomper::Extensions::Common
|
8
|
+
|
9
|
+
# The underlying {Stomper::Connection connection} to transmit frames through.
|
10
|
+
# @return [Stomper::Connection]
|
11
|
+
attr_reader :connection
|
12
|
+
# The headers to apply to all frames generated on this scope.
|
13
|
+
# @return [{Symbol => String}]
|
14
|
+
attr_reader :headers
|
15
|
+
|
16
|
+
# Creates a new {Stomper::Scopes::HeaderScope}. The supplied +headers+ hash will have
|
17
|
+
# all of its keys converted to symbols and its values converted to strings,
|
18
|
+
# so the key/value pairs must support this transformation (through +to_sym+
|
19
|
+
# and +to_s+, respectively.)
|
20
|
+
# @param [Stomper::Connection] connection
|
21
|
+
# @param [{Object => String}] headers
|
22
|
+
def initialize(connection, headers)
|
23
|
+
@headers = ::Stomper::Support.keys_to_sym(headers)
|
24
|
+
@connection = connection
|
25
|
+
::Stomper::Extensions::Common.extend_by_protocol_version(self, @connection.version)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Applies this scope to a block.
|
29
|
+
def apply_to(callback)
|
30
|
+
callback.call(self) if callback
|
31
|
+
end
|
32
|
+
|
33
|
+
# Transmits a frame, applying the set headers. After merging its headers
|
34
|
+
# into the frame, the frame is passed to the underlying connection for
|
35
|
+
# transmission.
|
36
|
+
# @param [Stomper::Frame] frame
|
37
|
+
def transmit(frame)
|
38
|
+
frame.headers.reverse_merge!(@headers)
|
39
|
+
@connection.transmit frame
|
40
|
+
end
|
41
|
+
|
42
|
+
# Returns the connection's {Stomper::ReceiptManager}
|
43
|
+
# @return [Stomper::ReceiptManager]
|
44
|
+
def receipt_manager; @connection.receipt_manager; end
|
45
|
+
|
46
|
+
# Returns the connection's {Stomper::SubscriptionManager}
|
47
|
+
# @return [Stomper::SubscriptionManager]
|
48
|
+
def subscription_manager; @connection.subscription_manager; end
|
49
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
# Automatically generates "receipt" headers, if none are present and
|
4
|
+
# applies a supplied callback to every receipt received for frames generated
|
5
|
+
# through it. As instances of this class rely on event callbacks attached
|
6
|
+
# to the underlying {Stomper::Connection connection}, it is entirely possible
|
7
|
+
# for those events to be triggered on +Thread+ other than main. It is for
|
8
|
+
# this reason that synchronization is used to ensure the integrity of
|
9
|
+
# the internal list of receipt IDs that have not yet been processed through
|
10
|
+
# the callback.
|
11
|
+
class Stomper::Scopes::ReceiptScope < ::Stomper::Scopes::HeaderScope
|
12
|
+
# A list of frames that support being receipted.
|
13
|
+
# @return [Array<String>]
|
14
|
+
FRAME_COMMANDS = %w(SEND SUBSCRIBE UNSUBSCRIBE
|
15
|
+
BEGIN COMMIT ABORT ACK NACK DISCONNECT)
|
16
|
+
|
17
|
+
# Create a new receipt scope. All receiptable frames transmitted through
|
18
|
+
# this instance will use the same callback for handling the RECEIPT frame
|
19
|
+
# sent by the broker.
|
20
|
+
def initialize(connection, headers)
|
21
|
+
super
|
22
|
+
@receipt_handler = nil
|
23
|
+
end
|
24
|
+
|
25
|
+
# Takes a block as a callback to invoke when a receipt is received.
|
26
|
+
def apply_to(callback)
|
27
|
+
@receipt_handler = callback
|
28
|
+
end
|
29
|
+
|
30
|
+
# Transmits a frame. This method will add an auto-generated +receipt+ header
|
31
|
+
# to the frame if one has not been set, and then set up a handler for the
|
32
|
+
# +receipt+ value, invoking the callback set through {#apply_to} when
|
33
|
+
# the corresponding RECEIPT frame is received from the broker.
|
34
|
+
# @param [Stomper::Frame] frame
|
35
|
+
def transmit(frame)
|
36
|
+
if @receipt_handler && FRAME_COMMANDS.include?(frame.command)
|
37
|
+
r_id = frame[:receipt]
|
38
|
+
r_id = ::Stomper::Support.next_serial if r_id.nil? || r_id.empty?
|
39
|
+
receipt_manager.add(r_id, @receipt_handler)
|
40
|
+
frame[:receipt] = r_id
|
41
|
+
end
|
42
|
+
super(frame)
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
# A "connection scope" that provides a convenient interface for handling
|
4
|
+
# transactions. In addition to the behaviors of {HeaderScope}, for frames
|
5
|
+
# that the Stomp protocol allows to be enclosed within a transaction, this
|
6
|
+
# scope automatically attaches a +transaction+ header.
|
7
|
+
class Stomper::Scopes::TransactionScope < ::Stomper::Scopes::HeaderScope
|
8
|
+
# A list of frames that support being part of a transaction.
|
9
|
+
# @return [Array<String>]
|
10
|
+
FRAME_COMMANDS = %w(SEND BEGIN COMMIT ABORT ACK NACK)
|
11
|
+
# The value assigned to the +transaction+ header.
|
12
|
+
# @return [String]
|
13
|
+
attr_reader :transaction
|
14
|
+
|
15
|
+
def initialize(connection, headers)
|
16
|
+
super
|
17
|
+
@headers[:transaction] ||= ::Stomper::Support.next_serial
|
18
|
+
@transaction = self.headers[:transaction]
|
19
|
+
@transaction_state = :pending
|
20
|
+
end
|
21
|
+
|
22
|
+
# Overrides the standard {Stomper::Extensions::Commmon#begin} behavior
|
23
|
+
# to start the transaction encapsulated by this {TransactionScope transaction}.
|
24
|
+
def begin_with_transaction(headers={})
|
25
|
+
if transaction_pending?
|
26
|
+
@transaction_state = :starting
|
27
|
+
else
|
28
|
+
raise ::Stomper::Errors::TransactionStartedError unless transaction_pending?
|
29
|
+
end
|
30
|
+
begin_without_transaction(@transaction, headers).tap do |f|
|
31
|
+
@transaction_state = :started
|
32
|
+
end
|
33
|
+
end
|
34
|
+
alias :begin_without_transaction :begin
|
35
|
+
alias :begin :begin_with_transaction
|
36
|
+
|
37
|
+
# Overrides the standard {Stomper::Extensions::Commmon#abort} behavior
|
38
|
+
# to abort the transaction encapsulated by this {TransactionScope transaction}.
|
39
|
+
def abort_with_transaction(headers={})
|
40
|
+
abort_without_transaction(@transaction, headers).tap do |f|
|
41
|
+
@transaction_state = :aborted
|
42
|
+
end
|
43
|
+
end
|
44
|
+
alias :abort_without_transaction :abort
|
45
|
+
alias :abort :abort_with_transaction
|
46
|
+
|
47
|
+
# Overrides the standard {Stomper::Extensions::Commmon#commit} behavior
|
48
|
+
# to commit the transaction encapsulated by this {TransactionScope transaction}.
|
49
|
+
def commit_with_transaction(headers={})
|
50
|
+
commit_without_transaction(@transaction, headers).tap do |f|
|
51
|
+
@transaction_state = :committed
|
52
|
+
end
|
53
|
+
end
|
54
|
+
alias :commit_without_transaction :commit
|
55
|
+
alias :commit :commit_with_transaction
|
56
|
+
|
57
|
+
# Transmits a frame, but only applies the +transaction+ header if the
|
58
|
+
# frame command is amongst those commands that can be included in a
|
59
|
+
# transaction.
|
60
|
+
# @param [Stomper::Frame] frame
|
61
|
+
def transmit(frame)
|
62
|
+
self.begin if transaction_pending?
|
63
|
+
if FRAME_COMMANDS.include? frame.command
|
64
|
+
if frame.command != 'BEGIN' && transaction_finalized?
|
65
|
+
raise ::Stomper::Errors::TransactionFinalizedError
|
66
|
+
end
|
67
|
+
super(frame)
|
68
|
+
else
|
69
|
+
@connection.transmit frame
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# Applies this transaction to a block. Before any transactionable frame
|
74
|
+
# is transmitted within the block, a BEGIN frame will be generated. If
|
75
|
+
# the block completes without raising an error, a COMMIT frame will be
|
76
|
+
# transmitted to complete the transaction, otherwise an ABORT frame will
|
77
|
+
# be transmitted signalling that the transaction should be rolled-back by
|
78
|
+
# the broker.
|
79
|
+
def apply_to(callback)
|
80
|
+
begin
|
81
|
+
super
|
82
|
+
self.commit if transaction_started?
|
83
|
+
rescue Exception => err
|
84
|
+
self.abort if transaction_started?
|
85
|
+
raise err
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# Returns true if a BEGIN frame has not yet been transmitted for this
|
90
|
+
# transaction, false otherwise.
|
91
|
+
# @return [true, false]
|
92
|
+
def transaction_pending?; @transaction_state == :pending; end
|
93
|
+
# Returns true if a BEGIN frame has been transmitted for this
|
94
|
+
# transaction but neither COMMIT nor ABORT have been sent, false otherwise.
|
95
|
+
# @return [true, false]
|
96
|
+
def transaction_started?; @transaction_state == :started; end
|
97
|
+
# Returns true if a COMMIT frame has been transmitted for this
|
98
|
+
# transaction, false otherwise.
|
99
|
+
# @return [true, false]
|
100
|
+
def transaction_committed?; @transaction_state == :committed; end
|
101
|
+
# Returns true if an ABORT frame has been transmitted for this
|
102
|
+
# transaction, false otherwise.
|
103
|
+
# @return [true, false]
|
104
|
+
def transaction_aborted?; @transaction_state == :aborted; end
|
105
|
+
# Returns true if a COMMIT or ABORT frame has been transmitted for this
|
106
|
+
# transaction, false otherwise.
|
107
|
+
# @return [true, false]
|
108
|
+
def transaction_finalized?; transaction_aborted? || transaction_committed?; end
|
109
|
+
end
|
data/lib/stomper/sockets.rb
CHANGED
@@ -1,37 +1,75 @@
|
|
1
|
-
|
2
|
-
module Sockets
|
3
|
-
class SSL < DelegateClass(OpenSSL::SSL::SSLSocket)
|
4
|
-
include FrameReader
|
5
|
-
include FrameWriter
|
1
|
+
# -*- encoding: utf-8 -*-
|
6
2
|
|
7
|
-
|
8
|
-
|
9
|
-
|
3
|
+
# Socket helpers.
|
4
|
+
module Stomper::Sockets
|
5
|
+
|
6
|
+
# A wrapper for an SSL Socket that tidies up the SSL specifics so
|
7
|
+
# our Connection library isn't troubled by them.
|
8
|
+
class SSL < DelegateClass(::OpenSSL::SSL::SSLSocket)
|
9
|
+
# Default SSL options to use with new {SSL} connections.
|
10
|
+
# @return {Symbol => Object}
|
11
|
+
DEFAULT_SSL_OPTIONS = {
|
12
|
+
:verify_mode => ::OpenSSL::SSL::VERIFY_PEER |
|
13
|
+
::OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT,
|
14
|
+
:ca_file => nil,
|
15
|
+
:ca_path => nil,
|
16
|
+
:cert => nil,
|
17
|
+
:key => nil,
|
18
|
+
:post_connection_check => true
|
19
|
+
}
|
20
|
+
|
21
|
+
# Create a new {SSL} connection to +host+ on +port+.
|
22
|
+
# @param [String] host hostname or IP address to connect to
|
23
|
+
# @param [Fixnum] port port number to establish the connection on
|
24
|
+
# @option opts [Object] :verify_mode (OpenSSL::SSL::VERIFY_PEER | OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT)
|
25
|
+
# the methodology to use when verifying SSL certificates
|
26
|
+
# @option opts [String] :ca_file (nil) A single file containing all known
|
27
|
+
# certificates for each certificate authority (CA)
|
28
|
+
# @option opts [String] :ca_path (nil) An openssl hashed directory of
|
29
|
+
# individual certificate files for each CA
|
30
|
+
# @option opts [OpenSSL::X509::Certificate] :cert (nil) Client's
|
31
|
+
# certificate. This is needed when server requires client to validate
|
32
|
+
# itself with a certificate.
|
33
|
+
# @option opts [OpenSSL::PKey::PKey] :key (nil) Client's private key.
|
34
|
+
# This is needed when server requires client to validate itself with
|
35
|
+
# a certificate.
|
36
|
+
def initialize(host, port, ssl_opts={})
|
37
|
+
ssl_opts = DEFAULT_SSL_OPTIONS.merge(ssl_opts)
|
10
38
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
@socket.connect
|
15
|
-
super(@socket)
|
16
|
-
end
|
17
|
-
|
18
|
-
def ready?
|
19
|
-
@socket.io.ready?
|
20
|
-
end
|
39
|
+
@context = ::OpenSSL::SSL::SSLContext.new
|
40
|
+
post_check = ssl_opts.delete(:post_connection_check)
|
41
|
+
post_check_host = (post_check == true) ? host : post_check
|
21
42
|
|
22
|
-
|
23
|
-
@
|
43
|
+
DEFAULT_SSL_OPTIONS.keys.each do |k|
|
44
|
+
@context.__send__(:"#{k}=", ssl_opts[k]) if ssl_opts.key?(k)
|
24
45
|
end
|
25
|
-
end
|
26
46
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
@socket
|
33
|
-
super(@socket)
|
47
|
+
tcp_sock = ::TCPSocket.new(host, port)
|
48
|
+
@socket = ::OpenSSL::SSL::SSLSocket.new(tcp_sock, @context)
|
49
|
+
@socket.sync_close = true
|
50
|
+
@socket.connect
|
51
|
+
if post_check_host
|
52
|
+
@socket.post_connection_check(post_check_host)
|
34
53
|
end
|
54
|
+
super(@socket)
|
55
|
+
end
|
56
|
+
|
57
|
+
# Passes the :ready? message on to the socket's underlying io object.
|
58
|
+
def ready?; @socket.io.ready?; end
|
59
|
+
|
60
|
+
# Passes the :shutdown message on to the socket's underlying io object.
|
61
|
+
def shutdown(mode=2); @socket.io.shutdown(mode); end
|
62
|
+
end
|
63
|
+
|
64
|
+
# A wrapper for an TCP Socket that tidies up the specifics so
|
65
|
+
# our Connection library isn't troubled by them.
|
66
|
+
class TCP < DelegateClass(::TCPSocket)
|
67
|
+
# Create a new {TCP} connection to +host+ on +port+.
|
68
|
+
# @param [String] host hostname or IP address to connect to
|
69
|
+
# @param [Fixnum] port port number to establish the connection on
|
70
|
+
def initialize(host, port)
|
71
|
+
@socket = TCPSocket.new(host, port)
|
72
|
+
super(@socket)
|
35
73
|
end
|
36
74
|
end
|
37
75
|
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
# Manages subscriptions for a {Stomper::Connection}
|
4
|
+
class Stomper::SubscriptionManager
|
5
|
+
# Creates a new subscription handler for the supplied {Stomper::Connection connection}
|
6
|
+
# @param [Stomper::Connection] connection
|
7
|
+
def initialize(connection)
|
8
|
+
@mon = ::Monitor.new
|
9
|
+
@callbacks = {}
|
10
|
+
@dests_to_ids = {}
|
11
|
+
connection.on_message { |m, con| dispatch(m) }
|
12
|
+
connection.on_unsubscribe { |u, con| remove(u) }
|
13
|
+
end
|
14
|
+
|
15
|
+
# Adds a callback handler for a MESSAGE frame that is sent via the subscription
|
16
|
+
# associated with the supplied SUBSCRIBE frame.
|
17
|
+
# @param [Stomper::Frame] subscribe SUBSCRIBE frame for the subscription
|
18
|
+
# @param [Proc] callback Proc to invoke when a matching MESSAGE frame is
|
19
|
+
# received from the broker.
|
20
|
+
# @return [self]
|
21
|
+
def add(subscribe, callback)
|
22
|
+
s_id = subscribe[:id]
|
23
|
+
dest = subscribe[:destination]
|
24
|
+
@mon.synchronize do
|
25
|
+
@callbacks[s_id] = callback
|
26
|
+
@dests_to_ids[dest] ||= []
|
27
|
+
@dests_to_ids[dest] << s_id
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# Returns true if the subscription ID is registered
|
32
|
+
# @param [String] id
|
33
|
+
# @return [true,false]
|
34
|
+
def subscribed_id?(id)
|
35
|
+
@mon.synchronize { @callbacks.key? id }
|
36
|
+
end
|
37
|
+
|
38
|
+
# Returns true if the subscription destination is registered
|
39
|
+
# @param [String] destination
|
40
|
+
# @return [true,false]
|
41
|
+
def subscribed_destination?(destination)
|
42
|
+
@mon.synchronize { @dests_to_ids.key? destination }
|
43
|
+
end
|
44
|
+
|
45
|
+
# Returns an array of subscription IDs that correspond to
|
46
|
+
# the given subscription destination. If the destination is unknown,
|
47
|
+
# returns +nil+.
|
48
|
+
# @param [String] destination
|
49
|
+
# @return [Array<String>, nil]
|
50
|
+
def ids_for_destination(destination)
|
51
|
+
@mon.synchronize { @dests_to_ids[destination] && @dests_to_ids[destination].dup }
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
def remove(unsub)
|
56
|
+
s_id = unsub[:id]
|
57
|
+
@mon.synchronize do
|
58
|
+
@dests_to_ids.each do |dest, ids|
|
59
|
+
ids.delete s_id
|
60
|
+
@dests_to_ids.delete dest if ids.empty?
|
61
|
+
end
|
62
|
+
@callbacks.delete(s_id)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def dispatch(message)
|
67
|
+
s_id = message[:subscription]
|
68
|
+
dest = message[:destination]
|
69
|
+
if s_id.nil? || s_id.empty?
|
70
|
+
cbs = @mon.synchronize do
|
71
|
+
@dests_to_ids[dest] && @dests_to_ids[dest].map { |id| @callbacks[id] }
|
72
|
+
end
|
73
|
+
cbs && cbs.each { |cb| cb.call(message) }
|
74
|
+
else
|
75
|
+
cb = @mon.synchronize { @callbacks[s_id] }
|
76
|
+
cb && cb.call(message)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|