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
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
|
+
|