stomper 1.0.0 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +5 -0
- data/{spec/spec.opts → .rspec} +0 -2
- data/Gemfile +4 -0
- data/LICENSE +201 -201
- data/README.md +130 -0
- data/Rakefile +5 -0
- data/examples/basic.rb +38 -0
- data/examples/events.rb +54 -0
- data/features/acking_messages.feature +147 -0
- data/features/disconnecting.feature +12 -0
- data/features/establish_connection.feature +44 -0
- data/features/protocol_version_negotiation.feature +61 -0
- data/features/receipts.feature +72 -0
- data/features/scopes.feature +32 -0
- data/features/secure_connections.feature +38 -0
- data/features/send_and_message.feature +28 -0
- data/features/steps/acking_messages_steps.rb +39 -0
- data/features/steps/disconnecting_steps.rb +8 -0
- data/features/steps/establish_connection_steps.rb +74 -0
- data/features/steps/frame_transmission_steps.rb +35 -0
- data/features/steps/protocol_version_negotiation_steps.rb +15 -0
- data/features/steps/receipts_steps.rb +79 -0
- data/features/steps/scopes_steps.rb +52 -0
- data/features/steps/secure_connections_steps.rb +41 -0
- data/features/steps/send_and_message_steps.rb +35 -0
- data/features/steps/subscribing_steps.rb +36 -0
- data/features/steps/threaded_receiver_steps.rb +8 -0
- data/features/steps/transactions_steps.rb +0 -0
- data/features/subscribing.feature +151 -0
- data/features/support/env.rb +11 -0
- data/features/support/header_helpers.rb +12 -0
- data/features/support/ssl/README +6 -0
- data/features/support/ssl/broker_cert.csr +17 -0
- data/features/support/ssl/broker_cert.pem +72 -0
- data/features/support/ssl/broker_key.pem +27 -0
- data/features/support/ssl/client_cert.csr +17 -0
- data/features/support/ssl/client_cert.pem +72 -0
- data/features/support/ssl/client_key.pem +27 -0
- data/features/support/ssl/demoCA/cacert.pem +17 -0
- data/features/support/ssl/demoCA/index.txt +2 -0
- data/features/support/ssl/demoCA/index.txt.attr +1 -0
- data/features/support/ssl/demoCA/index.txt.attr.old +1 -0
- data/features/support/ssl/demoCA/index.txt.old +1 -0
- data/features/support/ssl/demoCA/newcerts/01.pem +72 -0
- data/features/support/ssl/demoCA/newcerts/02.pem +72 -0
- data/features/support/ssl/demoCA/private/cakey.pem +17 -0
- data/features/support/ssl/demoCA/serial +1 -0
- data/features/support/ssl/demoCA/serial.old +1 -0
- data/features/support/test_stomp_server.rb +150 -0
- data/features/threaded_receiver.feature +11 -0
- data/features/transactions.feature +66 -0
- data/lib/stomper.rb +30 -20
- data/lib/stomper/connection.rb +442 -102
- data/lib/stomper/errors.rb +59 -0
- data/lib/stomper/extensions.rb +10 -0
- data/lib/stomper/extensions/common.rb +258 -0
- data/lib/stomper/extensions/events.rb +213 -0
- data/lib/stomper/extensions/heartbeat.rb +101 -0
- data/lib/stomper/extensions/scoping.rb +56 -0
- data/lib/stomper/frame.rb +54 -0
- data/lib/stomper/frame_serializer.rb +217 -0
- data/lib/stomper/headers.rb +15 -0
- data/lib/stomper/receipt_manager.rb +36 -0
- data/lib/stomper/receivers.rb +7 -0
- data/lib/stomper/receivers/threaded.rb +71 -0
- data/lib/stomper/scopes.rb +9 -0
- data/lib/stomper/scopes/header_scope.rb +49 -0
- data/lib/stomper/scopes/receipt_scope.rb +44 -0
- data/lib/stomper/scopes/transaction_scope.rb +109 -0
- data/lib/stomper/sockets.rb +66 -28
- data/lib/stomper/subscription_manager.rb +79 -0
- data/lib/stomper/support.rb +68 -0
- data/lib/stomper/support/1.8/frame_serializer.rb +53 -0
- data/lib/stomper/support/1.8/headers.rb +183 -0
- data/lib/stomper/support/1.9/frame_serializer.rb +64 -0
- data/lib/stomper/support/1.9/headers.rb +172 -0
- data/lib/stomper/support/ruby.rb +13 -0
- data/lib/stomper/uris.rb +49 -0
- data/lib/stomper/version.rb +7 -0
- data/spec/spec_helper.rb +13 -9
- data/spec/stomper/connection_spec.rb +712 -0
- data/spec/stomper/extensions/common_spec.rb +187 -0
- data/spec/stomper/extensions/events_spec.rb +78 -0
- data/spec/stomper/extensions/heartbeat_spec.rb +103 -0
- data/spec/stomper/extensions/scoping_spec.rb +21 -0
- data/spec/stomper/frame_serializer_1.8_spec.rb +318 -0
- data/spec/stomper/frame_serializer_spec.rb +316 -0
- data/spec/stomper/frame_spec.rb +36 -0
- data/spec/stomper/headers_spec.rb +224 -0
- data/spec/stomper/receipt_manager_spec.rb +91 -0
- data/spec/stomper/receivers/threaded_spec.rb +116 -0
- data/spec/stomper/scopes/header_scope_spec.rb +42 -0
- data/spec/stomper/scopes/receipt_scope_spec.rb +51 -0
- data/spec/stomper/scopes/transaction_scope_spec.rb +183 -0
- data/spec/stomper/sockets_spec.rb +113 -0
- data/spec/stomper/subscription_manager_spec.rb +107 -0
- data/spec/stomper/support_spec.rb +69 -0
- data/spec/stomper/uris_spec.rb +54 -0
- data/spec/stomper_spec.rb +9 -0
- data/spec/support/custom_argument_matchers.rb +57 -0
- data/spec/support/existential_frame_matchers.rb +19 -0
- data/spec/support/frame_header_matchers.rb +10 -0
- data/stomper.gemspec +30 -0
- metadata +272 -97
- data/AUTHORS +0 -21
- data/CHANGELOG +0 -20
- data/README.rdoc +0 -120
- data/lib/stomper/client.rb +0 -34
- data/lib/stomper/frame_reader.rb +0 -73
- data/lib/stomper/frame_writer.rb +0 -21
- data/lib/stomper/frames.rb +0 -39
- data/lib/stomper/frames/abort.rb +0 -10
- data/lib/stomper/frames/ack.rb +0 -25
- data/lib/stomper/frames/begin.rb +0 -11
- data/lib/stomper/frames/client_frame.rb +0 -89
- data/lib/stomper/frames/commit.rb +0 -10
- data/lib/stomper/frames/connect.rb +0 -10
- data/lib/stomper/frames/connected.rb +0 -30
- data/lib/stomper/frames/disconnect.rb +0 -10
- data/lib/stomper/frames/error.rb +0 -21
- data/lib/stomper/frames/message.rb +0 -48
- data/lib/stomper/frames/receipt.rb +0 -19
- data/lib/stomper/frames/send.rb +0 -10
- data/lib/stomper/frames/server_frame.rb +0 -38
- data/lib/stomper/frames/subscribe.rb +0 -42
- data/lib/stomper/frames/unsubscribe.rb +0 -19
- data/lib/stomper/open_uri_interface.rb +0 -41
- data/lib/stomper/receipt_handlers.rb +0 -23
- data/lib/stomper/receiptor.rb +0 -38
- data/lib/stomper/subscriber.rb +0 -76
- data/lib/stomper/subscription.rb +0 -128
- data/lib/stomper/subscriptions.rb +0 -95
- data/lib/stomper/threaded_receiver.rb +0 -59
- data/lib/stomper/transaction.rb +0 -185
- data/lib/stomper/transactor.rb +0 -50
- data/lib/stomper/uri.rb +0 -55
- data/spec/client_spec.rb +0 -29
- data/spec/connection_spec.rb +0 -22
- data/spec/frame_reader_spec.rb +0 -37
- data/spec/frame_writer_spec.rb +0 -27
- data/spec/frames/client_frame_spec.rb +0 -66
- data/spec/frames/indirect_frame_spec.rb +0 -45
- data/spec/frames/server_frame_spec.rb +0 -85
- data/spec/open_uri_interface_spec.rb +0 -132
- data/spec/receiptor_spec.rb +0 -35
- data/spec/shared_connection_examples.rb +0 -79
- data/spec/subscriber_spec.rb +0 -77
- data/spec/subscription_spec.rb +0 -157
- data/spec/subscriptions_spec.rb +0 -145
- data/spec/threaded_receiver_spec.rb +0 -33
- data/spec/transaction_spec.rb +0 -139
- data/spec/transactor_spec.rb +0 -46
@@ -0,0 +1,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
|