onstomp 1.0.0pre1 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (75) hide show
  1. data/README.md +1 -1
  2. data/Rakefile +8 -0
  3. data/examples/openuri.rb +36 -0
  4. data/lib/onstomp.rb +4 -0
  5. data/lib/onstomp/client.rb +6 -5
  6. data/lib/onstomp/components.rb +0 -1
  7. data/lib/onstomp/components/frame_headers.rb +35 -38
  8. data/lib/onstomp/components/threaded_processor.rb +13 -0
  9. data/lib/onstomp/connections/base.rb +15 -8
  10. data/lib/onstomp/connections/stomp_1.rb +0 -6
  11. data/lib/onstomp/connections/stomp_1_0.rb +8 -0
  12. data/lib/onstomp/connections/stomp_1_1.rb +8 -0
  13. data/lib/onstomp/failover.rb +16 -0
  14. data/lib/onstomp/failover/buffers.rb +8 -0
  15. data/lib/onstomp/failover/buffers/written.rb +91 -0
  16. data/lib/onstomp/failover/client.rb +127 -0
  17. data/lib/onstomp/failover/failover_configurable.rb +63 -0
  18. data/lib/onstomp/failover/failover_events.rb +96 -0
  19. data/lib/onstomp/failover/new_with_failover.rb +20 -0
  20. data/lib/onstomp/failover/pools.rb +8 -0
  21. data/lib/onstomp/failover/pools/base.rb +39 -0
  22. data/lib/onstomp/failover/pools/round_robin.rb +17 -0
  23. data/lib/onstomp/failover/uri.rb +34 -0
  24. data/lib/onstomp/interfaces/client_configurable.rb +2 -6
  25. data/lib/onstomp/interfaces/client_events.rb +4 -0
  26. data/lib/onstomp/interfaces/connection_events.rb +3 -3
  27. data/lib/onstomp/interfaces/event_manager.rb +8 -0
  28. data/lib/onstomp/interfaces/uri_configurable.rb +7 -7
  29. data/lib/onstomp/open-uri.rb +37 -0
  30. data/lib/onstomp/open-uri/client_extensions.rb +88 -0
  31. data/lib/onstomp/open-uri/message_queue.rb +38 -0
  32. data/lib/onstomp/version.rb +1 -1
  33. data/spec/onstomp/client_spec.rb +1 -4
  34. data/spec/onstomp/components/frame_headers_spec.rb +2 -5
  35. data/spec/onstomp/connections/stomp_1_0_spec.rb +22 -0
  36. data/spec/onstomp/connections/stomp_1_1_spec.rb +22 -0
  37. data/spec/onstomp/connections/stomp_1_spec.rb +2 -19
  38. data/spec/onstomp/connections_spec.rb +4 -0
  39. data/spec/onstomp/failover/buffers/written_spec.rb +8 -0
  40. data/spec/onstomp/failover/client_spec.rb +38 -0
  41. data/spec/onstomp/failover/failover_events_spec.rb +75 -0
  42. data/spec/onstomp/failover/new_with_failover_spec.rb +16 -0
  43. data/spec/onstomp/failover/pools/base_spec.rb +54 -0
  44. data/spec/onstomp/failover/pools/round_robin_spec.rb +27 -0
  45. data/spec/onstomp/failover/uri_spec.rb +21 -0
  46. data/spec/onstomp/full_stacks/failover_spec.rb +55 -0
  47. data/spec/onstomp/full_stacks/onstomp_spec.rb +15 -0
  48. data/spec/onstomp/full_stacks/open-uri_spec.rb +40 -0
  49. data/spec/onstomp/full_stacks/ssl/README +6 -0
  50. data/spec/onstomp/full_stacks/ssl/broker_cert.csr +17 -0
  51. data/spec/onstomp/full_stacks/ssl/broker_cert.pem +72 -0
  52. data/spec/onstomp/full_stacks/ssl/broker_key.pem +27 -0
  53. data/spec/onstomp/full_stacks/ssl/client_cert.csr +17 -0
  54. data/spec/onstomp/full_stacks/ssl/client_cert.pem +72 -0
  55. data/spec/onstomp/full_stacks/ssl/client_key.pem +27 -0
  56. data/spec/onstomp/full_stacks/ssl/demoCA/cacert.pem +17 -0
  57. data/spec/onstomp/full_stacks/ssl/demoCA/index.txt +2 -0
  58. data/spec/onstomp/full_stacks/ssl/demoCA/index.txt.attr +1 -0
  59. data/spec/onstomp/full_stacks/ssl/demoCA/index.txt.attr.old +1 -0
  60. data/spec/onstomp/full_stacks/ssl/demoCA/index.txt.old +1 -0
  61. data/spec/onstomp/full_stacks/ssl/demoCA/newcerts/01.pem +72 -0
  62. data/spec/onstomp/full_stacks/ssl/demoCA/newcerts/02.pem +72 -0
  63. data/spec/onstomp/full_stacks/ssl/demoCA/private/cakey.pem +17 -0
  64. data/spec/onstomp/full_stacks/ssl/demoCA/serial +1 -0
  65. data/spec/onstomp/full_stacks/ssl/demoCA/serial.old +1 -0
  66. data/spec/onstomp/full_stacks/test_broker.rb +251 -0
  67. data/spec/onstomp/interfaces/connection_events_spec.rb +3 -1
  68. data/spec/onstomp/open-uri/client_extensions_spec.rb +113 -0
  69. data/spec/onstomp/open-uri/message_queue_spec.rb +29 -0
  70. data/spec/onstomp/open-uri_spec.rb +43 -0
  71. data/spec/spec_helper.rb +2 -0
  72. data/yard_extensions.rb +5 -1
  73. metadata +82 -8
  74. data/lib/onstomp/components/nil_processor.rb +0 -20
  75. data/spec/onstomp/components/nil_processor_spec.rb +0 -32
@@ -0,0 +1,127 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ # A failover client that wraps multiple {OnStomp::Client clients} and maintains
4
+ # a connection to one of these clients. Frames are sent to the currently
5
+ # connected client. If the connection is lost, a failover client will
6
+ # automatically reconnect to another client in the pool, re-transmit any
7
+ # necessary frames and resume operation.
8
+ class OnStomp::Failover::Client
9
+ include OnStomp::Failover::FailoverConfigurable
10
+ include OnStomp::Failover::FailoverEvents
11
+ include OnStomp::Interfaces::FrameMethods
12
+
13
+ # The class to use when instantiating a new {#client_pool}.
14
+ # Defaults to {OnStomp::Failover::Pools::RoundRobin}
15
+ # @return [Class]
16
+ attr_configurable_pool :pool
17
+ # The class to use when instantiating a new frame buffer.
18
+ # Defaults to {OnStomp::Failover::Buffers::Written}
19
+ # @return [Class]
20
+ attr_configurable_buffer :buffer
21
+ # The delay in seconds to wait between connection retries.
22
+ # Defaults to +10+.
23
+ # @return [Fixnum]
24
+ attr_configurable_int :retry_delay, :default => 10
25
+ # The maximum number of times to retry connecting during a reconnect
26
+ # loop. A non-positive number will force the failover client to try to
27
+ # reconnect indefinitely. Defaults to +0+
28
+ # @return [Fixnum]
29
+ attr_configurable_int :retry_attempts, :default => 0
30
+ # Whether or not to randomize the {#client_pool} before connecting through
31
+ # any of its {OnStomp::Client clients}. Defaults to +false+
32
+ # @return [true,false]
33
+ attr_configurable_bool :randomize, :default => false
34
+
35
+ attr_reader :uri, :client_pool, :active_client, :frame_buffer, :connection
36
+
37
+ def initialize(uris, options={})
38
+ if uris.is_a?(Array)
39
+ uris = "failover:(#{uris.map { |u| u.to_s }.join(',')})"
40
+ end
41
+ @client_mutex = Mutex.new
42
+ @uri = URI.parse(uris)
43
+ configure_configurable options
44
+ create_client_pool
45
+ @active_client = nil
46
+ @connection = nil
47
+ @frame_buffer = buffer.new self
48
+ @disconnecting = false
49
+ @client_ready = false
50
+ end
51
+
52
+ # Returns true if there is an {#active_client} and it is
53
+ # {OnStomp::Client#connected? connected}.
54
+ # @return [true,false,nil]
55
+ def connected?
56
+ active_client && active_client.connected?
57
+ end
58
+
59
+ # Transmits a frame to the {#active_client} if one exists.
60
+ # @return [OnStomp::Components::Frame,nil]
61
+ def transmit frame, cbs={}
62
+ active_client && active_client.transmit(frame, cbs)
63
+ end
64
+
65
+ # Connects to one of the clients in the {#client_pool}
66
+ # @return [self]
67
+ def connect
68
+ @disconnecting = false
69
+ unless reconnect
70
+ raise OnStomp::Failover::MaximumRetriesExceededError
71
+ end
72
+ self
73
+ end
74
+
75
+ # Ensures that a connection is properly established, then invokes
76
+ # {OnStomp::Client#disconnect disconnect} on the {#active_client}
77
+ def disconnect *args, &block
78
+ return unless active_client
79
+ @disconnecting = true
80
+ Thread.pass until @client_ready
81
+ active_client.disconnect *args, &block
82
+ end
83
+
84
+ private
85
+ def reconnect
86
+ @client_mutex.synchronize do
87
+ @client_ready = false
88
+ attempt = 1
89
+ until connected? || retry_exceeded?(attempt)
90
+ sleep_for_retry attempt
91
+ begin
92
+ trigger_failover_retry :before, attempt
93
+ @active_client = client_pool.next_client
94
+ # +reconnect+ could be called again within the marked range.
95
+ active_client.connect # <--- From here
96
+ @connection = active_client.connection
97
+ rescue Exception
98
+ trigger_failover_event :connect_failure, :on, active_client, $!.message
99
+ end
100
+ trigger_failover_retry :after, attempt
101
+ attempt += 1
102
+ end
103
+ connected?.tap do |b|
104
+ b && trigger_failover_event(:connected, :on, active_client)
105
+ @client_ready = b
106
+ end # <--- Until here
107
+ end
108
+ end
109
+
110
+ def retry_exceeded? attempt
111
+ retry_attempts > 0 && attempt > retry_attempts
112
+ end
113
+
114
+ def sleep_for_retry attempt
115
+ sleep(retry_delay) if retry_delay > 0 && attempt > 1
116
+ end
117
+
118
+ def create_client_pool
119
+ @client_pool = pool.new(uri.failover_uris)
120
+ on_connection_closed do |client, *_|
121
+ unless @disconnecting
122
+ trigger_failover_event(:lost, :on, active_client)
123
+ reconnect
124
+ end
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,63 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ # Module for configurable attributes specific to
4
+ # {OnStomp::Failover::Client failover} clients.
5
+ module OnStomp::Failover::FailoverConfigurable
6
+ # Includes {OnStomp::Interfaces::ClientConfigurable} into +base+ and
7
+ # extends {OnStomp::Failover::FailoverConfigurable::ClassMethods}
8
+ # @param [Module] base
9
+ def self.included(base)
10
+ base.__send__ :include, OnStomp::Interfaces::ClientConfigurable
11
+ base.extend ClassMethods
12
+ end
13
+
14
+ # Provides attribute methods for {OnStomp::Failover::Client failover}
15
+ # clients.
16
+ module ClassMethods
17
+ # Creates readable and writeable attributes that are automatically
18
+ # converted into integers.
19
+ def attr_configurable_int *args, &block
20
+ trans = attr_configurable_wrap lambda { |v| v.to_i }, block
21
+ attr_configurable_single(*args, &trans)
22
+ end
23
+
24
+ # Creates readable and writeable attributes that are automatically
25
+ # converted into boolean values. Assigning the attributes any of
26
+ # +true+, +'true'+, +'1'+ or +1+ will set the attribute to +true+, all
27
+ # other values with be treated as +false+. This method will also alias
28
+ # the reader methods with +attr_name?+
29
+ def attr_configurable_bool *args, &block
30
+ trans = attr_configurable_wrap lambda { |v|
31
+ [true, 'true', '1', 1].include?(v) }, block
32
+ attr_configurable_single(*args, &trans)
33
+ args.each do |a|
34
+ unless a.is_a?(Hash)
35
+ alias_method :"#{a}?", a
36
+ end
37
+ end
38
+ end
39
+
40
+ # Creates a readable and writeable attribute with the given name that
41
+ # defaults to the {OnStomp::Failover::Pools::RoundRobin}. Corresponds
42
+ # the the class to use when creating new
43
+ # {OnStomp::Failover::Client#client_pool client pools}.
44
+ # @param [Symbol] nm name of attribute
45
+ def attr_configurable_pool nm
46
+ attr_configurable_class(nm,
47
+ :default => OnStomp::Failover::Pools::RoundRobin) do |p|
48
+ p || OnStomp::Failover::Pools::RoundRobin
49
+ end
50
+ end
51
+
52
+ # Creates a readable and writeable attribute with the given name that
53
+ # defaults to the {OnStomp::Failover::Buffers::Written}. Corresponds
54
+ # the the class to use for frame buffering and de-buffering.
55
+ # @param [Symbol] nm name of attribute
56
+ def attr_configurable_buffer nm
57
+ attr_configurable_class(nm,
58
+ :default => OnStomp::Failover::Buffers::Written) do |b|
59
+ b || OnStomp::Failover::Buffers::Written
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,96 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ # Events mixin for {OnStomp::Failover::Client failover} clients.
4
+ module OnStomp::Failover::FailoverEvents
5
+ include OnStomp::Interfaces::EventManager
6
+
7
+ # We do this one using +class << self+ instead of the +self.included+ hook
8
+ # because we need 'create_client_event_method+ immediately.
9
+ class << self
10
+ # Creates a forwarded binding for client events.
11
+ def create_client_event_method name
12
+ module_eval "def #{name}(&block); bind_client_event(:#{name}, block); end"
13
+ end
14
+ end
15
+
16
+ # Create forwarded bindings for all {OnStomp::Client} events.
17
+ OnStomp::Interfaces::ClientEvents.event_methods.each do |ev|
18
+ create_client_event_method ev
19
+ end
20
+
21
+ # Binds a callback to {OnStomp::Client#on_connction_established}. This has
22
+ # to be handled directly because :on_connection_established isn't a true
23
+ # event.
24
+ def on_connection_established &block
25
+ bind_client_event(:on_connection_established, block)
26
+ end
27
+ # Binds a callback to {OnStomp::Client#on_connection_died}. This has
28
+ # to be handled directly because :on_connection_died isn't a true
29
+ # event.
30
+ def on_connection_died &block
31
+ bind_client_event(:on_connection_died, block)
32
+ end
33
+ # Binds a callback to {OnStomp::Client#on_connection_terminated}. This has
34
+ # to be handled directly because :on_connection_terminated isn't a true
35
+ # event.
36
+ def on_connection_terminated &block
37
+ bind_client_event(:on_connection_terminated, block)
38
+ end
39
+ # Binds a callback to {OnStomp::Client#on_connection_closed}. This has
40
+ # to be handled directly because :on_connection_closed isn't a true
41
+ # event.
42
+ def on_connection_closed &block
43
+ bind_client_event(:on_connection_closed, block)
44
+ end
45
+
46
+ # Sets up a forwarded event binding, applying it to all clients in
47
+ # {OnStomp::Failover::Client#client_pool}.
48
+ def bind_client_event(name, block)
49
+ client_pool.each do |client|
50
+ client.__send__ name do |*args|
51
+ if client == active_client
52
+ block.call *args
53
+ end
54
+ end
55
+ end
56
+ end
57
+
58
+ # Binds a callback to be invoked when a failover client is attempting to
59
+ # connect through a new {OnStomp::Client client} in its
60
+ # {OnStomp::Failover::Client#pool}.
61
+ # @yield [failover, attempt, client] callback invoked when event is triggered
62
+ # @yieldparam [OnStomp::Failover::Client] failover
63
+ # @yieldparam [Fixnum] attempt
64
+ # @yieldparam [OnStomp::Client] client
65
+ create_event_methods :failover_retry, :before, :after
66
+ # Binds a callback to be invoked when a failover client fails to establish
67
+ # a connection through a {OnStomp::Client client} while reconnecting.
68
+ # @yield [failover, client, error_message] callback invoked when event is triggered
69
+ # @yieldparam [OnStomp::Failover::Client] failover
70
+ # @yieldparam [OnStomp::Client] client
71
+ # @yieldparam [String] error_message
72
+ create_event_methods :failover_connect_failure, :on
73
+ #create_event_methods :failover_retries_exceeded, :on
74
+ # Binds a callback to be invoked when an established connection through a
75
+ # client is lost.
76
+ # @yield [failover, client] callback invoked when event is triggered
77
+ # @yieldparam [OnStomp::Failover::Client] failover
78
+ # @yieldparam [OnStomp::Client] client
79
+ create_event_methods :failover_lost, :on
80
+ # Binds a callback to be invoked when a connection through a
81
+ # client is established.
82
+ # @yield [failover, client] callback invoked when event is triggered
83
+ # @yieldparam [OnStomp::Failover::Client] failover
84
+ # @yieldparam [OnStomp::Client] client
85
+ create_event_methods :failover_connected, :on
86
+
87
+ # Triggers a failover retry event
88
+ def trigger_failover_retry pref, attempt
89
+ trigger_failover_event :retry, pref, attempt, self.active_client
90
+ end
91
+
92
+ # Triggers a general failover event
93
+ def trigger_failover_event ev, pref, *args
94
+ trigger_event :"#{pref}_failover_#{ev}", self, *args
95
+ end
96
+ end
@@ -0,0 +1,20 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ class OnStomp::Client
4
+ class << self
5
+ # Creates an alias chain for {OnStomp::Client.new} so that if
6
+ # a failover: URI or an array of URIs are passed to the constructor,
7
+ # a {OnStomp::Failover::Client failover} client is built instead.
8
+ # @return [OnStomp::Client,OnStomp::Failover::Client]
9
+ def new_with_failover(uri, options={})
10
+ if uri.is_a?(Array) || uri.to_s =~ /^failover:/i
11
+ OnStomp::Failover::Client.new(uri, options)
12
+ else
13
+ new_without_failover(uri, options)
14
+ end
15
+ end
16
+
17
+ alias :new_without_failover :new
18
+ alias :new :new_with_failover
19
+ end
20
+ end
@@ -0,0 +1,8 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ # Namespace for client pool managers.
4
+ module OnStomp::Failover::Pools
5
+ end
6
+
7
+ require 'onstomp/failover/pools/base'
8
+ require 'onstomp/failover/pools/round_robin'
@@ -0,0 +1,39 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ # An abstract pool of clients. This class manages the shared behaviors
4
+ # of client pools, but has no means of picking successive clients.
5
+ # Subclasses must define +next_client+ or pool will not function.
6
+ class OnStomp::Failover::Pools::Base
7
+ attr_reader :clients
8
+
9
+ # Creates a new client pool by mapping an array of URIs into an array of
10
+ # {OnStomp::Client clients}.
11
+ def initialize uris
12
+ @clients = uris.map do |u|
13
+ OnStomp::Client.new u
14
+ end
15
+ end
16
+
17
+ # Raises an error, because it is up to subclasses to define this behavior.
18
+ # @raise [StandardError]
19
+ def next_client
20
+ raise 'implemented in subclasses'
21
+ end
22
+
23
+ # Shuffles the client pool.
24
+ def shuffle!
25
+ clients.shuffle!
26
+ end
27
+
28
+ # Yields each client in the pool to the supplied block. Raises an error
29
+ # if no block is provided.
30
+ # @raise [ArgumentError] if no block is given
31
+ # @yield [client] block to call for each client in the pool
32
+ # @yieldparam [OnStomp::Client] client
33
+ # @return [self]
34
+ def each &block
35
+ raise ArgumentError, 'no block provided' unless block_given?
36
+ clients.each &block
37
+ self
38
+ end
39
+ end
@@ -0,0 +1,17 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ # A round-robin client pool. Clients are processed sequentially, and once
4
+ # all clients have been processed, the pool cycles back to the beginning.
5
+ class OnStomp::Failover::Pools::RoundRobin < OnStomp::Failover::Pools::Base
6
+ def initialize uris
7
+ super
8
+ @index = -1
9
+ end
10
+
11
+ # Returns the next sequential client in the pool
12
+ # @return [OnStomp::Client]
13
+ def next_client
14
+ @index = (@index + 1) % clients.size
15
+ clients[@index]
16
+ end
17
+ end
@@ -0,0 +1,34 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ # Namespace for failover related URI classes.
4
+ module OnStomp::Failover::URI
5
+ # A URI class for representing URIs with a 'failover' scheme.
6
+ class FAILOVER < OnStomp::Components::URI::STOMP
7
+ # Matches the internal URIs and query contained in
8
+ # the +opaque+ part of a failover: URI
9
+ FAILOVER_OPAQUE_REG = /^\(([^\)]+)\)(?:\?(.*))?/
10
+
11
+ attr_reader :failover_uris
12
+ def initialize(*args)
13
+ super
14
+ _split_opaque_
15
+ end
16
+
17
+ private
18
+ def _split_opaque_
19
+ if opaque =~ FAILOVER_OPAQUE_REG
20
+ furis, fquery = $1, $2
21
+ @failover_uris = furis.split(',').map { |u| ::URI.parse(u.strip) }
22
+ self.set_opaque nil
23
+ self.set_path ''
24
+ self.set_query fquery
25
+ else
26
+ raise OnStomp::Failover::InvalidFailoverURIError, self.to_s
27
+ end
28
+ end
29
+ end
30
+ end
31
+
32
+ module ::URI
33
+ @@schemes['FAILOVER'] = OnStomp::Failover::URI::FAILOVER
34
+ end
@@ -40,16 +40,12 @@ module OnStomp::Interfaces::ClientConfigurable
40
40
  end
41
41
 
42
42
  # Creates a readable and writeable attribute with the given name that
43
- # defaults to the {OnStomp::Components::ThreadedProcessor} and if set
44
- # to nil will instead use {OnStomp::Components::NilProcessor}. Corresponds
43
+ # defaults to the {OnStomp::Components::ThreadedProcessor}. Corresponds
45
44
  # the the class to use when create new processor instances when a client
46
45
  # is connected.
47
46
  # @param [Symbol] nm name of attribute
48
47
  def attr_configurable_processor nm
49
- attr_configurable_class(nm,
50
- :default => OnStomp::Components::ThreadedProcessor) do |pr|
51
- pr || OnStomp::Components::NilProcessor
52
- end
48
+ attr_configurable_class(nm, :default => OnStomp::Components::ThreadedProcessor)
53
49
  end
54
50
  end
55
51
  end
@@ -12,6 +12,10 @@ module OnStomp::Interfaces::ClientEvents
12
12
 
13
13
  # @group Client Frame Event Bindings
14
14
 
15
+ # Can't get +before+ because the CONNECT frame isn't transmitted by
16
+ # the client.
17
+ create_event_methods :connect, :on
18
+
15
19
  # Binds a callback to be invoked when an ACK frame is transmitted
16
20
  # @yield [frame, client] callback invoked when event is triggered
17
21
  # @yieldparam [OnStomp::Components::Frame] frame
@@ -42,8 +42,8 @@ module OnStomp::Interfaces::ConnectionEvents
42
42
 
43
43
  # Triggers a connection specific event.
44
44
  # @param [Symbol] event name
45
- def trigger_connection_event event
46
- trigger_event :"on_#{event}", self.client, self
45
+ def trigger_connection_event event, msg=''
46
+ trigger_event :"on_#{event}", self.client, self, msg
47
47
  end
48
48
 
49
49
  # Takes a hash of event bindings a {OnStomp::Client client} has stored
@@ -57,6 +57,6 @@ module OnStomp::Interfaces::ConnectionEvents
57
57
  ev_hash.each do |ev, cbs|
58
58
  cbs.each { |cb| bind_event(ev, cb) }
59
59
  end
60
- trigger_connection_event :established
60
+ trigger_connection_event :established, "STOMP #{self.version} connection negotiated"
61
61
  end
62
62
  end