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.
Files changed (152) hide show
  1. data/.gitignore +5 -0
  2. data/{spec/spec.opts → .rspec} +0 -2
  3. data/Gemfile +4 -0
  4. data/LICENSE +201 -201
  5. data/README.md +130 -0
  6. data/Rakefile +5 -0
  7. data/examples/basic.rb +38 -0
  8. data/examples/events.rb +54 -0
  9. data/features/acking_messages.feature +147 -0
  10. data/features/disconnecting.feature +12 -0
  11. data/features/establish_connection.feature +44 -0
  12. data/features/protocol_version_negotiation.feature +61 -0
  13. data/features/receipts.feature +72 -0
  14. data/features/scopes.feature +32 -0
  15. data/features/secure_connections.feature +38 -0
  16. data/features/send_and_message.feature +28 -0
  17. data/features/steps/acking_messages_steps.rb +39 -0
  18. data/features/steps/disconnecting_steps.rb +8 -0
  19. data/features/steps/establish_connection_steps.rb +74 -0
  20. data/features/steps/frame_transmission_steps.rb +35 -0
  21. data/features/steps/protocol_version_negotiation_steps.rb +15 -0
  22. data/features/steps/receipts_steps.rb +79 -0
  23. data/features/steps/scopes_steps.rb +52 -0
  24. data/features/steps/secure_connections_steps.rb +41 -0
  25. data/features/steps/send_and_message_steps.rb +35 -0
  26. data/features/steps/subscribing_steps.rb +36 -0
  27. data/features/steps/threaded_receiver_steps.rb +8 -0
  28. data/features/steps/transactions_steps.rb +0 -0
  29. data/features/subscribing.feature +151 -0
  30. data/features/support/env.rb +11 -0
  31. data/features/support/header_helpers.rb +12 -0
  32. data/features/support/ssl/README +6 -0
  33. data/features/support/ssl/broker_cert.csr +17 -0
  34. data/features/support/ssl/broker_cert.pem +72 -0
  35. data/features/support/ssl/broker_key.pem +27 -0
  36. data/features/support/ssl/client_cert.csr +17 -0
  37. data/features/support/ssl/client_cert.pem +72 -0
  38. data/features/support/ssl/client_key.pem +27 -0
  39. data/features/support/ssl/demoCA/cacert.pem +17 -0
  40. data/features/support/ssl/demoCA/index.txt +2 -0
  41. data/features/support/ssl/demoCA/index.txt.attr +1 -0
  42. data/features/support/ssl/demoCA/index.txt.attr.old +1 -0
  43. data/features/support/ssl/demoCA/index.txt.old +1 -0
  44. data/features/support/ssl/demoCA/newcerts/01.pem +72 -0
  45. data/features/support/ssl/demoCA/newcerts/02.pem +72 -0
  46. data/features/support/ssl/demoCA/private/cakey.pem +17 -0
  47. data/features/support/ssl/demoCA/serial +1 -0
  48. data/features/support/ssl/demoCA/serial.old +1 -0
  49. data/features/support/test_stomp_server.rb +150 -0
  50. data/features/threaded_receiver.feature +11 -0
  51. data/features/transactions.feature +66 -0
  52. data/lib/stomper.rb +30 -20
  53. data/lib/stomper/connection.rb +442 -102
  54. data/lib/stomper/errors.rb +59 -0
  55. data/lib/stomper/extensions.rb +10 -0
  56. data/lib/stomper/extensions/common.rb +258 -0
  57. data/lib/stomper/extensions/events.rb +213 -0
  58. data/lib/stomper/extensions/heartbeat.rb +101 -0
  59. data/lib/stomper/extensions/scoping.rb +56 -0
  60. data/lib/stomper/frame.rb +54 -0
  61. data/lib/stomper/frame_serializer.rb +217 -0
  62. data/lib/stomper/headers.rb +15 -0
  63. data/lib/stomper/receipt_manager.rb +36 -0
  64. data/lib/stomper/receivers.rb +7 -0
  65. data/lib/stomper/receivers/threaded.rb +71 -0
  66. data/lib/stomper/scopes.rb +9 -0
  67. data/lib/stomper/scopes/header_scope.rb +49 -0
  68. data/lib/stomper/scopes/receipt_scope.rb +44 -0
  69. data/lib/stomper/scopes/transaction_scope.rb +109 -0
  70. data/lib/stomper/sockets.rb +66 -28
  71. data/lib/stomper/subscription_manager.rb +79 -0
  72. data/lib/stomper/support.rb +68 -0
  73. data/lib/stomper/support/1.8/frame_serializer.rb +53 -0
  74. data/lib/stomper/support/1.8/headers.rb +183 -0
  75. data/lib/stomper/support/1.9/frame_serializer.rb +64 -0
  76. data/lib/stomper/support/1.9/headers.rb +172 -0
  77. data/lib/stomper/support/ruby.rb +13 -0
  78. data/lib/stomper/uris.rb +49 -0
  79. data/lib/stomper/version.rb +7 -0
  80. data/spec/spec_helper.rb +13 -9
  81. data/spec/stomper/connection_spec.rb +712 -0
  82. data/spec/stomper/extensions/common_spec.rb +187 -0
  83. data/spec/stomper/extensions/events_spec.rb +78 -0
  84. data/spec/stomper/extensions/heartbeat_spec.rb +103 -0
  85. data/spec/stomper/extensions/scoping_spec.rb +21 -0
  86. data/spec/stomper/frame_serializer_1.8_spec.rb +318 -0
  87. data/spec/stomper/frame_serializer_spec.rb +316 -0
  88. data/spec/stomper/frame_spec.rb +36 -0
  89. data/spec/stomper/headers_spec.rb +224 -0
  90. data/spec/stomper/receipt_manager_spec.rb +91 -0
  91. data/spec/stomper/receivers/threaded_spec.rb +116 -0
  92. data/spec/stomper/scopes/header_scope_spec.rb +42 -0
  93. data/spec/stomper/scopes/receipt_scope_spec.rb +51 -0
  94. data/spec/stomper/scopes/transaction_scope_spec.rb +183 -0
  95. data/spec/stomper/sockets_spec.rb +113 -0
  96. data/spec/stomper/subscription_manager_spec.rb +107 -0
  97. data/spec/stomper/support_spec.rb +69 -0
  98. data/spec/stomper/uris_spec.rb +54 -0
  99. data/spec/stomper_spec.rb +9 -0
  100. data/spec/support/custom_argument_matchers.rb +57 -0
  101. data/spec/support/existential_frame_matchers.rb +19 -0
  102. data/spec/support/frame_header_matchers.rb +10 -0
  103. data/stomper.gemspec +30 -0
  104. metadata +272 -97
  105. data/AUTHORS +0 -21
  106. data/CHANGELOG +0 -20
  107. data/README.rdoc +0 -120
  108. data/lib/stomper/client.rb +0 -34
  109. data/lib/stomper/frame_reader.rb +0 -73
  110. data/lib/stomper/frame_writer.rb +0 -21
  111. data/lib/stomper/frames.rb +0 -39
  112. data/lib/stomper/frames/abort.rb +0 -10
  113. data/lib/stomper/frames/ack.rb +0 -25
  114. data/lib/stomper/frames/begin.rb +0 -11
  115. data/lib/stomper/frames/client_frame.rb +0 -89
  116. data/lib/stomper/frames/commit.rb +0 -10
  117. data/lib/stomper/frames/connect.rb +0 -10
  118. data/lib/stomper/frames/connected.rb +0 -30
  119. data/lib/stomper/frames/disconnect.rb +0 -10
  120. data/lib/stomper/frames/error.rb +0 -21
  121. data/lib/stomper/frames/message.rb +0 -48
  122. data/lib/stomper/frames/receipt.rb +0 -19
  123. data/lib/stomper/frames/send.rb +0 -10
  124. data/lib/stomper/frames/server_frame.rb +0 -38
  125. data/lib/stomper/frames/subscribe.rb +0 -42
  126. data/lib/stomper/frames/unsubscribe.rb +0 -19
  127. data/lib/stomper/open_uri_interface.rb +0 -41
  128. data/lib/stomper/receipt_handlers.rb +0 -23
  129. data/lib/stomper/receiptor.rb +0 -38
  130. data/lib/stomper/subscriber.rb +0 -76
  131. data/lib/stomper/subscription.rb +0 -128
  132. data/lib/stomper/subscriptions.rb +0 -95
  133. data/lib/stomper/threaded_receiver.rb +0 -59
  134. data/lib/stomper/transaction.rb +0 -185
  135. data/lib/stomper/transactor.rb +0 -50
  136. data/lib/stomper/uri.rb +0 -55
  137. data/spec/client_spec.rb +0 -29
  138. data/spec/connection_spec.rb +0 -22
  139. data/spec/frame_reader_spec.rb +0 -37
  140. data/spec/frame_writer_spec.rb +0 -27
  141. data/spec/frames/client_frame_spec.rb +0 -66
  142. data/spec/frames/indirect_frame_spec.rb +0 -45
  143. data/spec/frames/server_frame_spec.rb +0 -85
  144. data/spec/open_uri_interface_spec.rb +0 -132
  145. data/spec/receiptor_spec.rb +0 -35
  146. data/spec/shared_connection_examples.rb +0 -79
  147. data/spec/subscriber_spec.rb +0 -77
  148. data/spec/subscription_spec.rb +0 -157
  149. data/spec/subscriptions_spec.rb +0 -145
  150. data/spec/threaded_receiver_spec.rb +0 -33
  151. data/spec/transaction_spec.rb +0 -139
  152. 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,7 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ # Namespace for {Stomper::Connection} receivers
4
+ module Stomper::Receivers
5
+ end
6
+
7
+ require 'stomper/receivers/threaded'
@@ -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,9 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ # Module containing connection scopes.
4
+ module Stomper::Scopes
5
+ end
6
+
7
+ require 'stomper/scopes/header_scope'
8
+ require 'stomper/scopes/receipt_scope'
9
+ require 'stomper/scopes/transaction_scope'
@@ -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
@@ -1,37 +1,75 @@
1
- module Stomper
2
- module Sockets
3
- class SSL < DelegateClass(OpenSSL::SSL::SSLSocket)
4
- include FrameReader
5
- include FrameWriter
1
+ # -*- encoding: utf-8 -*-
6
2
 
7
- def initialize(host, port, *args)
8
- @context = OpenSSL::SSL::SSLContext.new
9
- @context.verify_mode = OpenSSL::SSL::VERIFY_NONE
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
- tcp_sock = TCPSocket.new(host, port)
12
- @socket = OpenSSL::SSL::SSLSocket.new(tcp_sock, @context)
13
- @socket.sync_close = true
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
- def shutdown(mode=2)
23
- @socket.io.shutdown(mode)
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
- class TCP < DelegateClass(TCPSocket)
28
- include FrameReader
29
- include FrameWriter
30
-
31
- def initialize(host, port, *args)
32
- @socket = TCPSocket.new(host, port)
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