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
data/lib/stomper.rb
CHANGED
@@ -1,27 +1,37 @@
|
|
1
|
-
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
# For extensions to URI.parse for Stomp schemes.
|
2
4
|
require 'uri'
|
3
|
-
|
5
|
+
# Primarily for CGI.parse
|
6
|
+
require 'cgi'
|
7
|
+
# Sockets are fairly important in all of this.
|
4
8
|
require 'socket'
|
9
|
+
# As is openssl
|
10
|
+
require 'openssl'
|
11
|
+
# For IO#ready?
|
12
|
+
require 'io/wait'
|
13
|
+
# The socket helpers use this to delegate to the real sockets
|
14
|
+
require 'delegate'
|
15
|
+
# Threading and Mutex support
|
5
16
|
require 'thread'
|
17
|
+
# Monitor support (prevent recursive dead locking)
|
6
18
|
require 'monitor'
|
7
|
-
require 'openssl'
|
8
|
-
require 'stomper/uri'
|
9
|
-
require 'stomper/frames'
|
10
|
-
require 'stomper/frame_reader'
|
11
|
-
require 'stomper/frame_writer'
|
12
|
-
require 'stomper/sockets'
|
13
|
-
require 'stomper/client'
|
14
|
-
require 'stomper/transactor'
|
15
|
-
require 'stomper/subscriber'
|
16
|
-
require 'stomper/receiptor'
|
17
|
-
require 'stomper/open_uri_interface'
|
18
|
-
require 'stomper/threaded_receiver'
|
19
|
-
require 'stomper/connection'
|
20
|
-
require 'stomper/transaction'
|
21
|
-
require 'stomper/subscription'
|
22
|
-
require 'stomper/subscriptions'
|
23
|
-
require 'stomper/receipt_handlers'
|
24
19
|
|
20
|
+
# Primary namespace of the stomper gem.
|
25
21
|
module Stomper
|
26
|
-
class MalformedFrameError < StandardError; end
|
27
22
|
end
|
23
|
+
|
24
|
+
require 'stomper/version'
|
25
|
+
require 'stomper/errors'
|
26
|
+
require 'stomper/headers'
|
27
|
+
require 'stomper/sockets'
|
28
|
+
require 'stomper/frame'
|
29
|
+
require 'stomper/uris'
|
30
|
+
require 'stomper/frame_serializer'
|
31
|
+
require 'stomper/subscription_manager'
|
32
|
+
require 'stomper/receipt_manager'
|
33
|
+
require 'stomper/receivers'
|
34
|
+
require 'stomper/extensions'
|
35
|
+
require 'stomper/scopes'
|
36
|
+
require 'stomper/support'
|
37
|
+
require 'stomper/connection'
|
data/lib/stomper/connection.rb
CHANGED
@@ -1,120 +1,460 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
# This class encapsulates a client connection to a message broker through the
|
4
|
+
# Stomp protocol. This class is also aliased as +Stomper::Client+
|
5
|
+
class Stomper::Connection
|
6
|
+
include ::Stomper::Extensions::Common
|
7
|
+
include ::Stomper::Extensions::Scoping
|
8
|
+
include ::Stomper::Extensions::Events
|
9
|
+
include ::Stomper::Extensions::Heartbeat
|
10
|
+
|
11
|
+
# The list of supported protocol versions
|
12
|
+
# @return [Array<String>]
|
13
|
+
PROTOCOL_VERSIONS = ['1.0', '1.1']
|
14
|
+
|
15
|
+
# The default configuration for connections. These settings have been
|
16
|
+
# deliberately left unfrozen to allow users to change defaults for all
|
17
|
+
# connections in one fell swoop.
|
18
|
+
# @return [{Symbol => Object}]
|
19
|
+
DEFAULT_CONFIG = {
|
20
|
+
:versions => ['1.0', '1.1'],
|
21
|
+
:heartbeats => [0, 0],
|
22
|
+
:host => nil,
|
23
|
+
:login => nil,
|
24
|
+
:passcode => nil,
|
25
|
+
:receiver_class => ::Stomper::Receivers::Threaded
|
26
|
+
}
|
13
27
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
28
|
+
# The URI representation of the broker this connection is associated with
|
29
|
+
# @return [URI]
|
30
|
+
attr_reader :uri
|
31
|
+
|
32
|
+
# The CONNECTED frame sent by the broker during the connection handshake.
|
33
|
+
# @return [Stomper::Frame,nil]
|
34
|
+
attr_reader :connected_frame
|
35
|
+
|
36
|
+
# The protocol versions to allow for this connection
|
37
|
+
# @return [Array<String>]
|
38
|
+
attr_reader :versions
|
39
|
+
|
40
|
+
# The protocol version negotiated between the client and broker. Will be
|
41
|
+
# +nil+ until the connection has been established.
|
42
|
+
# @return [String,nil]
|
43
|
+
attr_reader :version
|
44
|
+
|
45
|
+
# The client-side heartbeat settings to allow for this connection
|
46
|
+
# @return [Array<Fixnum>]
|
47
|
+
attr_reader :heartbeats
|
48
|
+
|
49
|
+
# The negotiated heartbeat strategy. The first element is the maximum
|
50
|
+
# number of milliseconds that the client can go without transmitting
|
51
|
+
# data or a heartbeat (a zero indicates that a client does not need to
|
52
|
+
# send heartbeats.) The second elemenet is the maximum number of milliseconds
|
53
|
+
# a server will go without transmitting data or a heartbeat (a zero indicates
|
54
|
+
# that the server need not send any heartbeats.)
|
55
|
+
# @return [Array<Fixnum>]
|
56
|
+
attr_reader :heartbeating
|
57
|
+
|
58
|
+
# The SSL options to use if this connection is secure
|
59
|
+
# @return [{Symbol => Object}, nil]
|
60
|
+
attr_reader :ssl
|
61
|
+
|
62
|
+
# The host header value to send to the broker when connecting. This allows
|
63
|
+
# the client to inform the server which host it wishes to connect with
|
64
|
+
# when multiple brokers may share an IP address through virtual hosting.
|
65
|
+
# @return [String]
|
66
|
+
attr_reader :host
|
67
|
+
|
68
|
+
# The login header value to send to the broker when connecting.
|
69
|
+
# @return [String]
|
70
|
+
attr_reader :login
|
71
|
+
|
72
|
+
# The passcode header value to send to the broker when connecting.
|
73
|
+
# @return [String]
|
74
|
+
attr_reader :passcode
|
75
|
+
|
76
|
+
# The class to use when instantiating a new receiver for the connection.
|
77
|
+
# Defaults to {Stomper::Receivers::Threaded}
|
78
|
+
# @return [CLass]
|
79
|
+
attr_reader :receiver_class
|
80
|
+
|
81
|
+
# A timestamp set to the last time a frame was transmitted. Returns +nil+
|
82
|
+
# if no frames have been transmitted yet
|
83
|
+
# @return [Time,nil]
|
84
|
+
attr_reader :last_transmitted_at
|
85
|
+
|
86
|
+
# A timestamp set to the last time a frame was received. Returns +nil+
|
87
|
+
# if no frames have been received yet
|
88
|
+
# @return [Time,nil]
|
89
|
+
attr_reader :last_received_at
|
90
|
+
|
91
|
+
# The subscription manager. Maintains the list of destinations subscribed
|
92
|
+
# to as well as the callbacks to invoke when a MESSAGE frame is received
|
93
|
+
# on one of them.
|
94
|
+
# @return [Stomper::SubscriptionManager]
|
95
|
+
attr_reader :subscription_manager
|
96
|
+
|
97
|
+
# The receipt manager. Maintains the list of receipt IDs and the callbacks
|
98
|
+
# associated with them that will be invoked when any frame with a matching
|
99
|
+
# +receipt-id+ header is received.
|
100
|
+
# @return [Stomper::ReceiptManager]
|
101
|
+
attr_reader :receipt_manager
|
102
|
+
|
103
|
+
|
104
|
+
# Creates a connection to a broker specified by the suppled uri. The given
|
105
|
+
# uri will be resolved to a URI instance through +URI.parse+. The final URI object must
|
106
|
+
# provide a {::URI::STOMP#create_socket create_socket} method, or an error
|
107
|
+
# will be raised. Both {::URI::STOMP} and {::URI::STOMP_SSL} provide this
|
108
|
+
# method, so string URIs with a scheme of either "stomp" or "stomp+ssl" will
|
109
|
+
# work automatically.
|
110
|
+
# Most connection options can be supplied through query parameters specified in the URI or
|
111
|
+
# through an optional +Hash+ parameter. If the same option is configured in
|
112
|
+
# both the URI's parameters and the options hash, the options hash takes
|
113
|
+
# precedence. Certain options, those pertaining to SSL settings for
|
114
|
+
# instance, must be configured through the options hash.
|
115
|
+
#
|
116
|
+
# @param [String] uri a string representing the URI to the message broker's
|
117
|
+
# Stomp interface.
|
118
|
+
# @param [{Symbol => Object}] options additional options for the connection.
|
119
|
+
# @option options [Array<String>] :versions (['1.0', '1.1']) protocol versions
|
120
|
+
# this connection should allow.
|
121
|
+
# @option options [Array<Fixnum>] :heartbeats ([0, 0]) heartbeat timings for
|
122
|
+
# this connection in milliseconds (a zero indicates that heartbeating is
|
123
|
+
# not desired from the client or the broker)
|
124
|
+
# @option options [{Symbol => Object}] :ssl ({}) SSL specific options to
|
125
|
+
# pass on when creating an {Stomper::Sockets::SSL SSL connection}.
|
126
|
+
# @option options [String] :host (nil) Host name to pass as +host+ header
|
127
|
+
# on CONNECT frames (will use actual connection hostname if not set)
|
128
|
+
# @option options [String] :login (nil) Username to send as +login+ header
|
129
|
+
# for credential authenticated connections.
|
130
|
+
# @option options [String] :passcode (nil) Password to send as +passcode+ header
|
131
|
+
# for credential authenticated connections.
|
132
|
+
#
|
133
|
+
# @example Connecting to a broker on 'host.domain.tld' and a port of 12345
|
134
|
+
# con = Stomper::Connection.new('stomp://host.domain.tld:12345')
|
135
|
+
#
|
136
|
+
# @example Connecting with login credentials
|
137
|
+
# con = Stomper::Connection.new('stomp://username:secret@host.domain.tld')
|
138
|
+
#
|
139
|
+
# @example Connecting using Stomp protocol 1.1, sending client beats once per second, and no interest in server beats.
|
140
|
+
# con = Stomper::Connection.new('stomp://host/?versions=1.1&heartbeats=1000&heartbeats=0')
|
141
|
+
# con = Stomper::Connection.new('stomp://host', :versions => '1.1', :heartbeats => [1000, 0])
|
142
|
+
# # both result in:
|
143
|
+
# con.heartbeat #=> [1000, 0]
|
144
|
+
# con.versions #=> ['1.1']
|
145
|
+
#
|
146
|
+
# @example Repeated options in URI and options hash
|
147
|
+
# con = Stomper::Connection.new('stomp://host?versions=1.1&versions=1.0', :versions => '1.1')
|
148
|
+
# con.versions #=> '1.1'
|
149
|
+
# # In this case, the versions query parameter value +[1.1 , 1.0]+ is
|
150
|
+
# # overridden by the options hash setting +1.1+
|
151
|
+
def initialize(uri, options={})
|
152
|
+
@ssl = options.delete(:ssl) || {}
|
153
|
+
@uri = uri.is_a?(::URI) ? uri : ::URI.parse(uri)
|
154
|
+
config = ::Stomper::Support.keys_to_sym(::CGI.parse(@uri.query || '')).
|
155
|
+
merge(::Stomper::Support.keys_to_sym(options))
|
156
|
+
DEFAULT_CONFIG.each do |attr_name, def_val|
|
157
|
+
if config.key? attr_name
|
158
|
+
__send__ :"#{attr_name}=", config[attr_name]
|
159
|
+
elsif def_val
|
160
|
+
__send__ :"#{attr_name}=", def_val
|
19
161
|
end
|
20
|
-
alias_method :open, :connect
|
21
162
|
end
|
163
|
+
@host ||= (@uri.host||'localhost')
|
164
|
+
@login ||= (@uri.user || '')
|
165
|
+
@passcode ||= (@uri.password || '')
|
166
|
+
@connected = false
|
167
|
+
@heartbeating = [0,0]
|
168
|
+
@last_transmitted_at = @last_received_at = nil
|
169
|
+
@subscription_manager = ::Stomper::SubscriptionManager.new(self)
|
170
|
+
@receipt_manager = ::Stomper::ReceiptManager.new(self)
|
171
|
+
@connecting = false
|
172
|
+
@disconnecting = false
|
173
|
+
|
174
|
+
on_connected do |cf, con|
|
175
|
+
unless connected?
|
176
|
+
@connecting = false
|
177
|
+
@disconnecting = false
|
178
|
+
@version = (cf[:version].nil?||cf[:version].empty?) ? '1.0' : cf[:version]
|
179
|
+
unless @versions.include?(@version)
|
180
|
+
close
|
181
|
+
raise ::Stomper::Errors::UnsupportedProtocolVersionError,
|
182
|
+
"broker requested '#{@version}', client allows: #{@versions.inspect}"
|
183
|
+
end
|
184
|
+
c_x, c_y = @heartbeats
|
185
|
+
s_x, s_y = (cf[:'heart-beat'] || '0,0').split(',').map do |v|
|
186
|
+
vi = v.to_i
|
187
|
+
vi > 0 ? vi : 0
|
188
|
+
end
|
189
|
+
@heartbeating = [ (c_x == 0||s_y == 0 ? 0 : [c_x,s_y].max),
|
190
|
+
(c_y == 0||s_x == 0 ? 0 : [c_y,s_x].max) ]
|
22
191
|
|
23
|
-
|
24
|
-
# The +uri+ parameter may be either a URI object, or something that can
|
25
|
-
# be parsed by URI.parse, such as a string.
|
26
|
-
# Some examples of acceptable +uri+ forms include:
|
27
|
-
# [stomp:///] Connection will be made to 'localhost' on port 61613 with no login credentials.
|
28
|
-
# [stomp+ssl:///] Same as above, but connection will be made on port 61612 and wrapped by SSL.
|
29
|
-
# [stomp://user:pass@host.tld] Connection will be made to 'host.tld', authenticating as 'user' with a password of 'pass'.
|
30
|
-
# [stomp://user:pass@host.tld:86753] Connection will be made to 'host.tld' on port 86753, authenticating as above.
|
31
|
-
# [stomp://host.tld:86753] Connection will be made to 'host.tld' on port 86753, with no authentication.
|
32
|
-
#
|
33
|
-
# In order to wrap the connection with SSL, the schema of +uri+ must be 'stomp+ssl';
|
34
|
-
# however, if SSL is not required, the schema is essentially ignored.
|
35
|
-
# The default port for the 'stomp+ssl' schema is 61612, all other schemas
|
36
|
-
# default to port 61613.
|
37
|
-
def initialize(uri, opts={})
|
38
|
-
if opts.delete(:threaded_receiver) { true }
|
39
|
-
extend ::Stomper::ThreadedReceiver
|
192
|
+
extend_for_protocol
|
40
193
|
end
|
41
|
-
@uri = (uri.is_a?(URI) && uri) || URI.parse(uri)
|
42
|
-
raise ArgumentError, 'Expected URI schema to be one of stomp or stomp+ssl' unless @uri.respond_to?(:create_socket)
|
43
|
-
@connected = false
|
44
|
-
@writer = @reader = nil
|
45
194
|
end
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
# is created.
|
51
|
-
#
|
52
|
-
# See also: new
|
53
|
-
def connect
|
54
|
-
@connected = false
|
55
|
-
@socket = @uri.create_socket
|
56
|
-
transmit Stomper::Frames::Connect.new(@uri.user, @uri.password)
|
57
|
-
@connected = receive.instance_of?(Stomper::Frames::Connected)
|
195
|
+
|
196
|
+
on_disconnect do |df, con|
|
197
|
+
@disconnecting = true
|
198
|
+
close unless df[:receipt]
|
58
199
|
end
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
200
|
+
end
|
201
|
+
|
202
|
+
# Sets the protocol versions that are acceptable for this connection.
|
203
|
+
# This method accepts version numbers as:
|
204
|
+
# - A single string value (eg: '1.0')
|
205
|
+
# - An array of string values (eg: ['1.1', '1.0'])
|
206
|
+
# @overload versions=(version)
|
207
|
+
# Sets the acceptable versions to a single supplied version.
|
208
|
+
# @param [String] version the protocol version to accept
|
209
|
+
# @overload versions=(versions)
|
210
|
+
# Sets the acceptable versions to the list provided
|
211
|
+
# @param [Array<String>] versions list of acceptable protocol versions
|
212
|
+
# @return [Array<String>] acceptable protocol versions
|
213
|
+
# @raise [Stomper::Errors::UnsupportedProtocolVersionError] if none of the
|
214
|
+
# versions provided are supported by this library
|
215
|
+
def versions=(vers)
|
216
|
+
vers = [vers] unless vers.is_a?(Array)
|
217
|
+
@versions = PROTOCOL_VERSIONS.select { |v| vers.include? v }
|
218
|
+
if @versions.empty?
|
219
|
+
raise ::Stomper::Errors::UnsupportedProtocolVersionError, "no supported protocol versions in #{vers.inspect}"
|
64
220
|
end
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
221
|
+
@versions
|
222
|
+
end
|
223
|
+
|
224
|
+
# Sets the client-side heartbeat settings to allow for this connection.
|
225
|
+
# The first element specifies the smallest number of milliseconds between
|
226
|
+
# heartbeats the client can guarantee, a value of 0 indicates that the client
|
227
|
+
# cannot send heartbeats. The second element specifies the
|
228
|
+
# duration in milliseconds between heartbeats that the client would like
|
229
|
+
# to receive from the server, a value of 0 indicates that the client does not
|
230
|
+
# want to receive server heartbeats. Both values are converted to integers,
|
231
|
+
# and negative values are replaced with 0.
|
232
|
+
#
|
233
|
+
# @param [Array<Fixnum>] beats
|
234
|
+
def heartbeats=(beats)
|
235
|
+
@heartbeats = beats[0..1].map { |b| bi = b.to_i; bi > 0 ? bi : 0 }
|
236
|
+
end
|
237
|
+
|
238
|
+
# Sets the host header value to use when connecting to the server. This
|
239
|
+
# provides the client with the ability to specify a specific broker that
|
240
|
+
# resides on a server that supports virtual hosts.
|
241
|
+
#
|
242
|
+
# @param [String] val
|
243
|
+
def host=(val); @host = (val.is_a?(Array) ? val.first : val).to_s; end
|
244
|
+
|
245
|
+
# Sets the login header value to use when connecting to the server.
|
246
|
+
#
|
247
|
+
# @param [String] val
|
248
|
+
def login=(val); @login = (val.is_a?(Array) ? val.first : val).to_s; end
|
249
|
+
|
250
|
+
# Sets the passcode header value to use when connecting to the server.
|
251
|
+
#
|
252
|
+
# @param [String] val
|
253
|
+
def passcode=(val)
|
254
|
+
@passcode = (val.is_a?(Array) ? val.first : val).to_s
|
255
|
+
end
|
256
|
+
|
257
|
+
# Sets the class to use when a receiver needs to be created
|
258
|
+
# @see #start
|
259
|
+
# @see #stop
|
260
|
+
# @see Stomper::Receivers::Threaded
|
261
|
+
def receiver_class=(val)
|
262
|
+
@receiver_class = ::Stomper::Support.constantize(val.is_a?(Array) ?
|
263
|
+
val.first : val)
|
264
|
+
end
|
265
|
+
|
266
|
+
# Establishes a connection to the broker. After the socket connection is
|
267
|
+
# established, a CONNECT/STOMP frame will be sent to the broker and a frame
|
268
|
+
# will be read from the TCP stream. If the frame is a CONNECTED frame, the
|
269
|
+
# connection has been established and you're ready to go, otherwise the
|
270
|
+
# socket will be closed and an error will be raised.
|
271
|
+
def connect(headers={})
|
272
|
+
@socket = @uri.create_socket(@ssl)
|
273
|
+
@serializer = ::Stomper::FrameSerializer.new(@socket)
|
274
|
+
m_headers = {
|
275
|
+
:'accept-version' => @versions.join(','),
|
276
|
+
:host => @host,
|
277
|
+
:'heart-beat' => @heartbeats.join(','),
|
278
|
+
:login => @login,
|
279
|
+
:passcode => @passcode
|
280
|
+
}
|
281
|
+
@connecting = true
|
282
|
+
transmit create_frame('CONNECT', headers, m_headers)
|
283
|
+
receive.tap do |f|
|
284
|
+
if f.command == 'CONNECTED'
|
285
|
+
@connected_frame = f
|
286
|
+
else
|
287
|
+
close
|
288
|
+
raise ::Stomper::Errors::ConnectFailedError, 'broker did not send CONNECTED frame'
|
74
289
|
end
|
75
290
|
end
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
291
|
+
@connected = true
|
292
|
+
trigger_event(:on_connection_established, self) if @connected
|
293
|
+
@connected_frame
|
294
|
+
end
|
295
|
+
alias :open :connect
|
296
|
+
|
297
|
+
class << self
|
298
|
+
# Creates a new connection and immediately connects it to the broker.
|
299
|
+
# @see #initialize
|
300
|
+
def connect(uri, options={})
|
301
|
+
conx = new(uri, options)
|
302
|
+
conx.connect
|
303
|
+
conx
|
304
|
+
end
|
305
|
+
alias :open :connect
|
306
|
+
end
|
307
|
+
|
308
|
+
# True if a connection with the broker has been established, false otherwise.
|
309
|
+
# @return [true,false]
|
310
|
+
def connected?
|
311
|
+
@connected && !@socket.closed?
|
312
|
+
end
|
313
|
+
|
314
|
+
# Creates an instance of the class given by {#receiver_class} and starts it.
|
315
|
+
# A call to {#connect} will be made if the connection has not been established.
|
316
|
+
# The class to instantiate can be overridden on a per connection basis, or
|
317
|
+
# for all connections by changing DEFAULT_CONFIG[:receiver_class]
|
318
|
+
# @param [{Object => String}] headers optional headers to pass to {#connect}
|
319
|
+
# if the connection has not yet been established.
|
320
|
+
# @return [self]
|
321
|
+
# @see #stop
|
322
|
+
# @see #connect
|
323
|
+
def start(headers={})
|
324
|
+
connect(headers) unless @connected
|
325
|
+
@receiver ||= receiver_class.new(self)
|
326
|
+
@receiver.start
|
327
|
+
self
|
328
|
+
end
|
329
|
+
|
330
|
+
# Stops the instantiated receiver and calls {#disconnect} if a connection
|
331
|
+
# has been established.
|
332
|
+
# @param [{Object => String}] headers optional headers to pass to {#disconnect}
|
333
|
+
# if the connection has been established.
|
334
|
+
# @return [self]
|
335
|
+
# @raise [Exception] if invoking +stop+ on the receiver raises an exception
|
336
|
+
# @see #start
|
337
|
+
# @see #disconnect
|
338
|
+
# @see Stomper::Receivers::Threaded#stop for an example of when a receiver
|
339
|
+
# may raise an exception when stopped.
|
340
|
+
def stop(headers={})
|
341
|
+
disconnect(headers) unless @disconnecting
|
342
|
+
@receiver && @receiver.stop
|
343
|
+
self
|
344
|
+
end
|
345
|
+
|
346
|
+
# Returns true if the receiver exists and is running.
|
347
|
+
def running?
|
348
|
+
@receiver && @receiver.running?
|
349
|
+
end
|
350
|
+
|
351
|
+
# Disconnects from the broker immediately. This is not a polite disconnect,
|
352
|
+
# meaning that no DISCONNECT frame is transmitted to the broker, the socket
|
353
|
+
# is shutdown and closed immediately. Calls to {#disconnect} invoke this
|
354
|
+
# method internally after the DISCONNECT frame has been transmitted. This
|
355
|
+
# method always triggers the
|
356
|
+
# {Stomper::Extensions::Events#on_connection_closed on_connection_closed} event
|
357
|
+
# and if +true+ is passed as a parameter,
|
358
|
+
# {Stomper::Extensions::Events#on_connection_terminated on_connection_terminated}
|
359
|
+
# will be triggered as well.
|
360
|
+
# @see #disconnect
|
361
|
+
# @see Stomper::Extensions::Events#on_connection_closed
|
362
|
+
# @see Stomper::Extensions::Events#on_connection_terminated
|
363
|
+
# @param [true,false] fire_terminated (false) If true, trigger
|
364
|
+
# {Stomper::Extensions::Events#on_connection_terminated}
|
365
|
+
def close(fire_terminated=false)
|
366
|
+
begin
|
367
|
+
trigger_event(:on_connection_terminated, self) if fire_terminated
|
368
|
+
ensure
|
369
|
+
unless @socket.closed?
|
370
|
+
@socket.shutdown(2) rescue nil
|
371
|
+
@socket.close rescue nil
|
90
372
|
end
|
373
|
+
@connected = false
|
91
374
|
end
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
375
|
+
trigger_event(:on_connection_closed, self)
|
376
|
+
end
|
377
|
+
|
378
|
+
# Transmits a frame to the broker. This is a low-level method used internally
|
379
|
+
# by the more user friendly interface.
|
380
|
+
# @param [Stomper::Frame] frame
|
381
|
+
def transmit(frame)
|
382
|
+
trigger_event(:on_connection_died, self) if dead?
|
383
|
+
trigger_event(:before_transmitting, frame, self)
|
384
|
+
trigger_before_transmitted_frame(frame, self)
|
385
|
+
begin
|
386
|
+
@serializer.write_frame(frame).tap do
|
387
|
+
@last_transmitted_at = Time.now
|
388
|
+
trigger_event(:after_transmitting, frame, self)
|
389
|
+
trigger_transmitted_frame(frame, self)
|
390
|
+
end
|
391
|
+
rescue ::IOError, ::SystemCallError
|
392
|
+
close(true)
|
393
|
+
raise
|
394
|
+
end
|
395
|
+
end
|
396
|
+
|
397
|
+
# Receives a frame from the broker.
|
398
|
+
# @return [Stomper::Frame]
|
399
|
+
def receive
|
400
|
+
trigger_event(:on_connection_died, self) if dead?
|
401
|
+
if alive? || @connecting
|
402
|
+
trigger_event(:before_receiving, nil, self)
|
98
403
|
begin
|
99
|
-
@
|
100
|
-
|
101
|
-
|
102
|
-
|
404
|
+
@serializer.read_frame.tap do |f|
|
405
|
+
if f.nil?
|
406
|
+
close(true) if @connected
|
407
|
+
else
|
408
|
+
@last_received_at = Time.now
|
409
|
+
trigger_event(:after_receiving, f, self)
|
410
|
+
trigger_received_frame(f, self)
|
411
|
+
end
|
412
|
+
end
|
413
|
+
rescue ::IOError, ::SystemCallError
|
414
|
+
close(true)
|
415
|
+
raise
|
103
416
|
end
|
104
417
|
end
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
418
|
+
end
|
419
|
+
|
420
|
+
# Receives a frame from the broker if there is data to be read from the
|
421
|
+
# underlying socket. If there is no data available for reading from the
|
422
|
+
# socket, +nil+ is returned.
|
423
|
+
# @note While this method will not block if there is no data ready for reading,
|
424
|
+
# if any data is available it will block until a complete frame has been read.
|
425
|
+
# @return [Stomper::Frame, nil]
|
426
|
+
def receive_nonblock
|
427
|
+
trigger_event(:on_connection_died, self) if dead?
|
428
|
+
trigger_event(:before_receiving, self)
|
429
|
+
if @socket.ready?
|
430
|
+
@serializer.read_frame.tap do |f|
|
431
|
+
trigger_event(:after_receiving, self, f)
|
432
|
+
trigger_received_frame(f, self)
|
433
|
+
end
|
113
434
|
end
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
435
|
+
end
|
436
|
+
|
437
|
+
# Duration in milliseconds since a frame has been transmitted to the broker.
|
438
|
+
# @return [Fixnum]
|
439
|
+
def duration_since_transmitted
|
440
|
+
@last_transmitted_at && ((Time.now - @last_transmitted_at)*1000).to_i
|
441
|
+
end
|
442
|
+
|
443
|
+
# Duration in milliseconds since a frame has been received from the broker.
|
444
|
+
# @return [Fixnum]
|
445
|
+
def duration_since_received
|
446
|
+
@last_received_at && ((Time.now - @last_received_at)*1000).to_i
|
447
|
+
end
|
448
|
+
|
449
|
+
private
|
450
|
+
def extend_for_protocol
|
451
|
+
::Stomper::Extensions::Common.extend_by_protocol_version(self, @version)
|
452
|
+
::Stomper::Extensions::Heartbeat.extend_by_protocol_version(self, @version)
|
453
|
+
@serializer.extend_for_protocol @version
|
454
|
+
self
|
119
455
|
end
|
120
456
|
end
|
457
|
+
|
458
|
+
# Alias Stomper::Client to Stomper::Connection
|
459
|
+
::Stomper::Client = ::Stomper::Connection
|
460
|
+
|