ably 1.1.4 → 1.1.8
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.
- checksums.yaml +4 -4
- data/.github/workflows/check.yml +15 -1
- data/CHANGELOG.md +109 -0
- data/COPYRIGHT +1 -1
- data/README.md +23 -9
- data/SPEC.md +289 -228
- data/ably.gemspec +14 -9
- data/lib/ably/agent.rb +3 -0
- data/lib/ably/exceptions.rb +6 -0
- data/lib/ably/models/connection_details.rb +8 -0
- data/lib/ably/models/delta_extras.rb +29 -0
- data/lib/ably/models/error_info.rb +6 -2
- data/lib/ably/models/message.rb +25 -0
- data/lib/ably/models/presence_message.rb +14 -0
- data/lib/ably/models/protocol_message.rb +13 -8
- data/lib/ably/modules/ably.rb +11 -1
- data/lib/ably/realtime/channel/channel_manager.rb +2 -2
- data/lib/ably/realtime/channel/channel_state_machine.rb +5 -1
- data/lib/ably/realtime/channel/publisher.rb +6 -0
- data/lib/ably/realtime/channel.rb +2 -0
- data/lib/ably/realtime/client/incoming_message_dispatcher.rb +14 -6
- data/lib/ably/realtime/client.rb +1 -0
- data/lib/ably/realtime/connection/connection_manager.rb +13 -4
- data/lib/ably/realtime/connection/connection_state_machine.rb +4 -0
- data/lib/ably/realtime/connection.rb +2 -2
- data/lib/ably/rest/channel.rb +11 -3
- data/lib/ably/rest/client.rb +37 -18
- data/lib/ably/rest/middleware/fail_if_unsupported_mime_type.rb +4 -1
- data/lib/ably/version.rb +1 -13
- data/lib/ably.rb +1 -0
- data/spec/acceptance/realtime/auth_spec.rb +1 -1
- data/spec/acceptance/realtime/channel_history_spec.rb +25 -0
- data/spec/acceptance/realtime/channel_spec.rb +220 -1
- data/spec/acceptance/realtime/connection_failures_spec.rb +85 -13
- data/spec/acceptance/realtime/connection_spec.rb +263 -32
- data/spec/acceptance/realtime/presence_history_spec.rb +3 -1
- data/spec/acceptance/realtime/presence_spec.rb +31 -159
- data/spec/acceptance/rest/base_spec.rb +8 -4
- data/spec/acceptance/rest/channel_spec.rb +84 -9
- data/spec/acceptance/rest/channels_spec.rb +1 -1
- data/spec/acceptance/rest/client_spec.rb +72 -33
- data/spec/shared/client_initializer_behaviour.rb +131 -0
- data/spec/shared/model_behaviour.rb +1 -1
- data/spec/spec_helper.rb +11 -2
- data/spec/support/test_app.rb +1 -1
- data/spec/unit/models/delta_extras_spec.rb +14 -0
- data/spec/unit/models/error_info_spec.rb +17 -1
- data/spec/unit/models/message_spec.rb +83 -0
- data/spec/unit/models/presence_message_spec.rb +49 -0
- data/spec/unit/models/protocol_message_spec.rb +72 -20
- data/spec/unit/realtime/channel_spec.rb +3 -2
- data/spec/unit/realtime/channels_spec.rb +3 -3
- data/spec/unit/realtime/incoming_message_dispatcher_spec.rb +38 -0
- data/spec/unit/rest/channel_spec.rb +44 -1
- data/spec/unit/rest/client_spec.rb +47 -0
- metadata +48 -36
data/ably.gemspec
CHANGED
@@ -20,9 +20,9 @@ Gem::Specification.new do |spec|
|
|
20
20
|
|
21
21
|
spec.add_runtime_dependency 'eventmachine', '~> 1.2.6'
|
22
22
|
spec.add_runtime_dependency 'em-http-request', '~> 1.1'
|
23
|
-
spec.add_runtime_dependency 'statesman', '~>
|
24
|
-
spec.add_runtime_dependency 'faraday', '
|
25
|
-
spec.add_runtime_dependency '
|
23
|
+
spec.add_runtime_dependency 'statesman', '~> 8.0'
|
24
|
+
spec.add_runtime_dependency 'faraday', '~> 1.0'
|
25
|
+
spec.add_runtime_dependency 'typhoeus', '~> 1.4'
|
26
26
|
|
27
27
|
if RUBY_VERSION.match(/^1\./)
|
28
28
|
spec.add_runtime_dependency 'json', '< 2.0'
|
@@ -33,9 +33,9 @@ Gem::Specification.new do |spec|
|
|
33
33
|
spec.add_runtime_dependency 'msgpack', '>= 1.3.0'
|
34
34
|
spec.add_runtime_dependency 'addressable', '>= 2.0.0'
|
35
35
|
|
36
|
-
spec.add_development_dependency 'rake', '~>
|
36
|
+
spec.add_development_dependency 'rake', '~> 13.0'
|
37
37
|
spec.add_development_dependency 'redcarpet', '~> 3.3'
|
38
|
-
spec.add_development_dependency 'rspec', '~> 3.
|
38
|
+
spec.add_development_dependency 'rspec', '~> 3.10.0'
|
39
39
|
spec.add_development_dependency 'rspec-retry', '~> 0.6'
|
40
40
|
spec.add_development_dependency 'yard', '~> 0.9'
|
41
41
|
spec.add_development_dependency 'rspec-instafail', '~> 1.0'
|
@@ -47,11 +47,16 @@ Gem::Specification.new do |spec|
|
|
47
47
|
spec.add_development_dependency 'parallel_tests', '~> 2.9.0'
|
48
48
|
else
|
49
49
|
spec.add_development_dependency 'webmock', '~> 3.11'
|
50
|
-
spec.add_development_dependency '
|
51
|
-
spec.add_development_dependency '
|
50
|
+
spec.add_development_dependency 'simplecov', '~> 0.21.2'
|
51
|
+
spec.add_development_dependency 'simplecov-lcov', '~> 0.8.0'
|
52
|
+
spec.add_development_dependency 'parallel_tests', '~> 3.7'
|
52
53
|
if !RUBY_VERSION.match(/^2\.[0123]/)
|
53
|
-
spec.add_development_dependency 'pry'
|
54
|
-
spec.add_development_dependency 'pry-byebug'
|
54
|
+
spec.add_development_dependency 'pry', '~> 0.14.1'
|
55
|
+
spec.add_development_dependency 'pry-byebug', '~> 3.8.0'
|
55
56
|
end
|
56
57
|
end
|
58
|
+
|
59
|
+
if RUBY_VERSION.match(/^3\./)
|
60
|
+
spec.add_development_dependency 'webrick', '~> 1.7.0'
|
61
|
+
end
|
57
62
|
end
|
data/lib/ably/agent.rb
ADDED
data/lib/ably/exceptions.rb
CHANGED
@@ -52,6 +52,12 @@ module Ably
|
|
52
52
|
end
|
53
53
|
end
|
54
54
|
|
55
|
+
# Maximum frame size exceeded TO3l9
|
56
|
+
class MaxFrameSizeExceeded < BaseAblyException; end
|
57
|
+
|
58
|
+
# Maximum message size exceeded TO3l8
|
59
|
+
class MaxMessageSizeExceeded < BaseAblyException; end
|
60
|
+
|
55
61
|
# An invalid request was received by Ably
|
56
62
|
class InvalidRequest < BaseAblyException; end
|
57
63
|
|
@@ -21,6 +21,12 @@ module Ably::Models
|
|
21
21
|
class ConnectionDetails
|
22
22
|
include Ably::Modules::ModelCommon
|
23
23
|
|
24
|
+
# Max message size
|
25
|
+
MAX_MESSAGE_SIZE = 65536 # See spec TO3l8
|
26
|
+
|
27
|
+
# Max frame size
|
28
|
+
MAX_FRAME_SIZE = 524288 # See spec TO3l9
|
29
|
+
|
24
30
|
# @param attributes [Hash]
|
25
31
|
# @option attributes [String] :client_id contains the client ID assigned to the connection
|
26
32
|
# @option attributes [String] :connection_key the connection secret key string that is used to resume a connection and its state
|
@@ -38,6 +44,8 @@ module Ably::Models
|
|
38
44
|
self.attributes[duration_field] = (self.attributes[duration_field].to_f / 1000).round
|
39
45
|
end
|
40
46
|
end
|
47
|
+
self.attributes[:max_message_size] ||= MAX_MESSAGE_SIZE
|
48
|
+
self.attributes[:max_frame_size] ||= MAX_FRAME_SIZE
|
41
49
|
self.attributes.freeze
|
42
50
|
end
|
43
51
|
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Ably::Models
|
2
|
+
#
|
3
|
+
# @!attribute [r] from
|
4
|
+
# @return [String] The id of the message the delta was generated from
|
5
|
+
# @!attribute [r] format
|
6
|
+
# @return [String] The delta format. Only vcdiff is supported as at API version 1.2
|
7
|
+
#
|
8
|
+
class DeltaExtras
|
9
|
+
include Ably::Modules::ModelCommon
|
10
|
+
|
11
|
+
# The id of the message the delta was generated from.
|
12
|
+
# @return [String, nil]
|
13
|
+
#
|
14
|
+
attr_reader :from
|
15
|
+
|
16
|
+
# The delta format.
|
17
|
+
# @return [String, nil]
|
18
|
+
#
|
19
|
+
attr_reader :format
|
20
|
+
|
21
|
+
def initialize(attributes = {})
|
22
|
+
@from, @format = IdiomaticRubyWrapper((attributes || {}), stop_at: [:from, :format]).attributes.values_at(:from, :format)
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_json(*args)
|
26
|
+
as_json(args).to_json
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -27,6 +27,10 @@ module Ably::Models
|
|
27
27
|
# @return [Integer] Ably error code (see ably-common/protocol/errors.json)
|
28
28
|
# @!attribute [r] status
|
29
29
|
# @return [Integer] HTTP Status Code corresponding to this error, where applicable
|
30
|
+
# @!attribute [r] request_id
|
31
|
+
# @return [Integer] HTTP RequestId corresponding to this error, where applicable (#RSC7c)
|
32
|
+
# @!attribute [r] cause
|
33
|
+
# @return [Integer] HTTP Status Code corresponding to this error, where applicable (#TI1)
|
30
34
|
# @!attribute [r] attributes
|
31
35
|
# @return [Hash] Access the protocol message Hash object ruby'fied to use symbolized keys
|
32
36
|
#
|
@@ -38,7 +42,7 @@ module Ably::Models
|
|
38
42
|
@hash_object = IdiomaticRubyWrapper(hash_object.clone.freeze)
|
39
43
|
end
|
40
44
|
|
41
|
-
%w(message code href status_code).each do |attribute|
|
45
|
+
%w(message code href status_code request_id cause).each do |attribute|
|
42
46
|
define_method attribute do
|
43
47
|
attributes[attribute.to_sym]
|
44
48
|
end
|
@@ -52,7 +56,7 @@ module Ably::Models
|
|
52
56
|
def to_s
|
53
57
|
error_href = href || (code ? "https://help.ably.io/error/#{code}" : '')
|
54
58
|
see_msg = " -> see #{error_href} for help" unless message.to_s.include?(error_href.to_s)
|
55
|
-
"<Error: #{message} (code: #{code}, http status: #{status})>#{see_msg}"
|
59
|
+
"<Error: #{message} (code: #{code}, http status: #{status} request_id: #{request_id} cause: #{cause})>#{see_msg}"
|
56
60
|
end
|
57
61
|
end
|
58
62
|
end
|
data/lib/ably/models/message.rb
CHANGED
@@ -105,6 +105,20 @@ module Ably::Models
|
|
105
105
|
end.to_json
|
106
106
|
end
|
107
107
|
|
108
|
+
# The size is the sum over name, data, clientId, and extras in bytes (TO3l8a)
|
109
|
+
#
|
110
|
+
def size
|
111
|
+
%w(name data client_id extras).map do |attr|
|
112
|
+
if (value = attributes[attr.to_sym]).is_a?(String)
|
113
|
+
value.bytesize
|
114
|
+
elsif value.nil?
|
115
|
+
0
|
116
|
+
else
|
117
|
+
value.to_json.bytesize
|
118
|
+
end
|
119
|
+
end.sum
|
120
|
+
end
|
121
|
+
|
108
122
|
# Assign this message to a ProtocolMessage before delivery to the Ably system
|
109
123
|
# @api private
|
110
124
|
def assign_to_protocol_message(protocol_message)
|
@@ -128,6 +142,9 @@ module Ably::Models
|
|
128
142
|
|
129
143
|
# Contains any arbitrary key value pairs which may also contain other primitive JSON types, JSON-encodable objects or JSON-encodable arrays.
|
130
144
|
# The extras field is provided to contain message metadata and/or ancillary payloads in support of specific functionality, e.g. push
|
145
|
+
# 1.2 adds the delta extension which is of type DeltaExtras, and the headers extension, which contains arbitrary string->string key-value pairs,
|
146
|
+
# settable at publish time. Unless otherwise specified, the client library should not attempt to do any filtering or validation of the extras
|
147
|
+
# field itself, but should treat it opaquely, encoding it and passing it to realtime unaltered.
|
131
148
|
# @api private
|
132
149
|
def extras
|
133
150
|
attributes[:extras].tap do |val|
|
@@ -137,6 +154,14 @@ module Ably::Models
|
|
137
154
|
end
|
138
155
|
end
|
139
156
|
|
157
|
+
# Delta extras extension (TM2i)
|
158
|
+
# @return [DeltaExtras, nil]
|
159
|
+
# @api private
|
160
|
+
def delta_extras
|
161
|
+
return nil if attributes[:extras][:delta].nil?
|
162
|
+
@delta_extras ||= DeltaExtras.new(attributes[:extras][:delta]).freeze
|
163
|
+
end
|
164
|
+
|
140
165
|
private
|
141
166
|
def raw_hash_object
|
142
167
|
@raw_hash_object
|
@@ -125,6 +125,20 @@ module Ably::Models
|
|
125
125
|
end.to_json
|
126
126
|
end
|
127
127
|
|
128
|
+
# The size is the sum over data and clientId in bytes (TO3l8a)
|
129
|
+
#
|
130
|
+
def size
|
131
|
+
%w(data client_id).map do |attr|
|
132
|
+
if (value = attributes[attr.to_sym]).is_a?(String)
|
133
|
+
value.bytesize
|
134
|
+
elsif value.nil?
|
135
|
+
0
|
136
|
+
else
|
137
|
+
value.to_json.bytesize
|
138
|
+
end
|
139
|
+
end.sum
|
140
|
+
end
|
141
|
+
|
128
142
|
# Assign this presence message to a ProtocolMessage before delivery to the Ably system
|
129
143
|
# @api private
|
130
144
|
def assign_to_protocol_message(protocol_message)
|
@@ -19,8 +19,6 @@ module Ably::Models
|
|
19
19
|
# @!attribute [r] channel_serial
|
20
20
|
# @return [String] Contains a serial number for a message on the current channel
|
21
21
|
# @!attribute [r] connection_id
|
22
|
-
# @return [String] Contains a string public identifier for the connection
|
23
|
-
# @!attribute [r] connection_key
|
24
22
|
# @return [String] Contains a string private connection key used to recover this connection
|
25
23
|
# @!attribute [r] connection_serial
|
26
24
|
# @return [Bignum] Contains a serial number for a message sent from the server to the client
|
@@ -98,12 +96,6 @@ module Ably::Models
|
|
98
96
|
end
|
99
97
|
end
|
100
98
|
|
101
|
-
def connection_key
|
102
|
-
# connection_key in connection details takes precedence over connection_key on the ProtocolMessage
|
103
|
-
# connection_key in the ProtocolMessage will be deprecated in future protocol versions > 0.8
|
104
|
-
connection_details.connection_key || attributes[:connection_key]
|
105
|
-
end
|
106
|
-
|
107
99
|
def id!
|
108
100
|
raise RuntimeError, 'ProtocolMessage #id is nil' unless id
|
109
101
|
id
|
@@ -185,6 +177,14 @@ module Ably::Models
|
|
185
177
|
end
|
186
178
|
end
|
187
179
|
|
180
|
+
def message_size
|
181
|
+
presence.map(&:size).sum + messages.map(&:size).sum
|
182
|
+
end
|
183
|
+
|
184
|
+
def has_correct_message_size?
|
185
|
+
message_size <= connection_details.max_message_size
|
186
|
+
end
|
187
|
+
|
188
188
|
def flags
|
189
189
|
Integer(attributes[:flags])
|
190
190
|
rescue TypeError
|
@@ -216,6 +216,11 @@ module Ably::Models
|
|
216
216
|
flags & 16 == 16 # 2^4
|
217
217
|
end
|
218
218
|
|
219
|
+
# @api private
|
220
|
+
def has_attach_resume_flag?
|
221
|
+
flags & 32 == 32 # 2^5
|
222
|
+
end
|
223
|
+
|
219
224
|
# @api private
|
220
225
|
def has_attach_presence_flag?
|
221
226
|
flags & 65536 == 65536 # 2^16
|
data/lib/ably/modules/ably.rb
CHANGED
@@ -6,8 +6,18 @@
|
|
6
6
|
module Ably
|
7
7
|
# Fallback hosts to use when a connection to rest/realtime.ably.io is not possible due to
|
8
8
|
# network failures either at the client, between the client and Ably, within an Ably data center, or at the IO domain registrar
|
9
|
+
# see https://docs.ably.io/client-lib-development-guide/features/#RSC15a
|
9
10
|
#
|
10
|
-
|
11
|
+
FALLBACK_DOMAIN = 'ably-realtime.com'.freeze
|
12
|
+
FALLBACK_IDS = %w(a b c d e).freeze
|
13
|
+
|
14
|
+
# Default production fallbacks a.ably-realtime.com ... e.ably-realtime.com
|
15
|
+
FALLBACK_HOSTS = FALLBACK_IDS.map { |host| "#{host}.#{FALLBACK_DOMAIN}".freeze }.freeze
|
16
|
+
|
17
|
+
# Custom environment default fallbacks {ENV}-a-fallback.ably-realtime.com ... {ENV}-a-fallback.ably-realtime.com
|
18
|
+
CUSTOM_ENVIRONMENT_FALLBACKS_SUFFIXES = FALLBACK_IDS.map do |host|
|
19
|
+
"-#{host}-fallback.#{FALLBACK_DOMAIN}".freeze
|
20
|
+
end.freeze
|
11
21
|
|
12
22
|
INTERNET_CHECK = {
|
13
23
|
url: '//internet-up.ably-realtime.com/is-the-internet-up.txt',
|
@@ -63,6 +63,8 @@ module Ably::Realtime
|
|
63
63
|
log_channel_error protocol_message.error
|
64
64
|
end
|
65
65
|
|
66
|
+
channel.properties.set_attach_serial(protocol_message.channel_serial)
|
67
|
+
|
66
68
|
if protocol_message.has_channel_resumed_flag?
|
67
69
|
logger.debug { "ChannelManager: Additional resumed ATTACHED message received for #{channel.state} channel '#{channel.name}'" }
|
68
70
|
else
|
@@ -75,8 +77,6 @@ module Ably::Realtime
|
|
75
77
|
)
|
76
78
|
update_presence_sync_state_following_attached protocol_message
|
77
79
|
end
|
78
|
-
|
79
|
-
channel.properties.set_attach_serial(protocol_message.channel_serial)
|
80
80
|
end
|
81
81
|
|
82
82
|
# Handle DETACED messages, see #RTL13 for server-initated detaches
|
@@ -26,12 +26,16 @@ module Ably::Realtime
|
|
26
26
|
transition :from => :detaching, :to => [:detached, :attaching, :attached, :failed, :suspended]
|
27
27
|
transition :from => :detached, :to => [:attaching, :attached, :failed]
|
28
28
|
transition :from => :suspended, :to => [:attaching, :attached, :detached, :failed]
|
29
|
-
transition :from => :failed, :to => [:attaching]
|
29
|
+
transition :from => :failed, :to => [:attaching, :initialized]
|
30
30
|
|
31
31
|
after_transition do |channel, transition|
|
32
32
|
channel.synchronize_state_with_statemachine
|
33
33
|
end
|
34
34
|
|
35
|
+
after_transition(to: [:initialized]) do |channel|
|
36
|
+
channel.clear_error_reason
|
37
|
+
end
|
38
|
+
|
35
39
|
after_transition(to: [:attaching]) do |channel|
|
36
40
|
channel.manager.attach
|
37
41
|
end
|
@@ -22,6 +22,12 @@ module Ably::Realtime
|
|
22
22
|
end
|
23
23
|
end
|
24
24
|
|
25
|
+
max_message_size = connection.details && connection.details.max_message_size || Ably::Models::ConnectionDetails::MAX_MESSAGE_SIZE
|
26
|
+
if messages.sum(&:size) > max_message_size
|
27
|
+
error = Ably::Exceptions::MaxMessageSizeExceeded.new("Message size exceeded #{max_message_size} bytes.")
|
28
|
+
return Ably::Util::SafeDeferrable.new_and_fail_immediately(logger, error)
|
29
|
+
end
|
30
|
+
|
25
31
|
connection.send_protocol_message(
|
26
32
|
action: Ably::Models::ProtocolMessage::ACTION.Message.to_i,
|
27
33
|
channel: channel_name,
|
@@ -323,6 +323,8 @@ module Ably
|
|
323
323
|
def update_options(channel_options)
|
324
324
|
@options = channel_options.clone.freeze
|
325
325
|
end
|
326
|
+
alias set_options update_options # (RSL7)
|
327
|
+
alias options= update_options
|
326
328
|
|
327
329
|
# Used by {Ably::Modules::StateEmitter} to debug state changes
|
328
330
|
# @api private
|
@@ -121,15 +121,23 @@ module Ably::Realtime
|
|
121
121
|
presence.manager.sync_process_messages protocol_message.channel_serial, protocol_message.presence
|
122
122
|
|
123
123
|
when ACTION.Presence
|
124
|
-
|
125
|
-
|
126
|
-
presence.
|
124
|
+
if protocol_message.has_correct_message_size?
|
125
|
+
presence = get_channel(protocol_message.channel).presence
|
126
|
+
protocol_message.presence.each do |presence_message|
|
127
|
+
presence.__incoming_msgbus__.publish :presence, presence_message
|
128
|
+
end
|
129
|
+
else
|
130
|
+
logger.fatal Ably::Exceptions::ProtocolError.new("Not published. Channel message limit exceeded #{protocol_message.message_size} bytes", 400, Ably::Exceptions::Codes::UNABLE_TO_RECOVER_CHANNEL_MESSAGE_LIMIT_EXCEEDED).message
|
127
131
|
end
|
128
132
|
|
129
133
|
when ACTION.Message
|
130
|
-
|
131
|
-
|
132
|
-
|
134
|
+
if protocol_message.has_correct_message_size?
|
135
|
+
channel = get_channel(protocol_message.channel)
|
136
|
+
protocol_message.messages.each do |message|
|
137
|
+
channel.__incoming_msgbus__.publish :message, message
|
138
|
+
end
|
139
|
+
else
|
140
|
+
logger.fatal Ably::Exceptions::ProtocolError.new("Not published. Channel message limit exceeded #{protocol_message.message_size} bytes", 400, Ably::Exceptions::Codes::UNABLE_TO_RECOVER_CHANNEL_MESSAGE_LIMIT_EXCEEDED).message
|
133
141
|
end
|
134
142
|
|
135
143
|
when ACTION.Auth
|
data/lib/ably/realtime/client.rb
CHANGED
@@ -74,6 +74,7 @@ module Ably
|
|
74
74
|
def_delegators :@rest_client, :use_tls?, :protocol, :protocol_binary?
|
75
75
|
def_delegators :@rest_client, :environment, :custom_host, :custom_port, :custom_tls_port
|
76
76
|
def_delegators :@rest_client, :log_level
|
77
|
+
def_delegators :@rest_client, :options
|
77
78
|
|
78
79
|
# Creates a {Ably::Realtime::Client Realtime Client} and configures the {Ably::Auth} object for the connection.
|
79
80
|
#
|
@@ -117,17 +117,17 @@ module Ably::Realtime
|
|
117
117
|
EventMachine.next_tick { connection.trigger_resumed }
|
118
118
|
resend_pending_message_ack_queue
|
119
119
|
else
|
120
|
-
logger.debug { "ConnectionManager: Connection was not resumed, old connection ID #{connection.id} has been updated with new connection ID #{protocol_message.connection_id} and key #{protocol_message.connection_key}" }
|
120
|
+
logger.debug { "ConnectionManager: Connection was not resumed, old connection ID #{connection.id} has been updated with new connection ID #{protocol_message.connection_id} and key #{protocol_message.connection_details.connection_key}" }
|
121
121
|
nack_messages_on_all_channels protocol_message.error
|
122
122
|
force_reattach_on_channels protocol_message.error
|
123
123
|
end
|
124
124
|
else
|
125
|
-
logger.debug { "ConnectionManager: New connection created with ID #{protocol_message.connection_id} and key #{protocol_message.connection_key}" }
|
125
|
+
logger.debug { "ConnectionManager: New connection created with ID #{protocol_message.connection_id} and key #{protocol_message.connection_details.connection_key}" }
|
126
126
|
end
|
127
127
|
|
128
128
|
reattach_suspended_channels protocol_message.error
|
129
129
|
|
130
|
-
connection.configure_new protocol_message.connection_id, protocol_message.connection_key, protocol_message.connection_serial
|
130
|
+
connection.configure_new protocol_message.connection_id, protocol_message.connection_details.connection_key, protocol_message.connection_serial
|
131
131
|
end
|
132
132
|
|
133
133
|
# When connection is CONNECTED and receives an update
|
@@ -139,7 +139,7 @@ module Ably::Realtime
|
|
139
139
|
# Update the connection details and any associated defaults
|
140
140
|
connection.set_connection_details protocol_message.connection_details
|
141
141
|
|
142
|
-
connection.configure_new protocol_message.connection_id, protocol_message.connection_key, protocol_message.connection_serial
|
142
|
+
connection.configure_new protocol_message.connection_id, protocol_message.connection_details.connection_key, protocol_message.connection_serial
|
143
143
|
|
144
144
|
state_change = Ably::Models::ConnectionStateChange.new(
|
145
145
|
current: connection.state,
|
@@ -319,6 +319,15 @@ module Ably::Realtime
|
|
319
319
|
end
|
320
320
|
end
|
321
321
|
|
322
|
+
# @api private
|
323
|
+
def reintialize_failed_chanels
|
324
|
+
channels.select do |channel|
|
325
|
+
channel.failed?
|
326
|
+
end.each do |channel|
|
327
|
+
channel.transition_state_machine :initialized
|
328
|
+
end
|
329
|
+
end
|
330
|
+
|
322
331
|
# When continuity on a connection is lost all messages
|
323
332
|
# whether queued or awaiting an ACK must be NACK'd as we now have a new connection
|
324
333
|
def nack_messages_on_all_channels(error)
|
@@ -36,6 +36,10 @@ module Ably::Realtime
|
|
36
36
|
connection.manager.setup_transport
|
37
37
|
end
|
38
38
|
|
39
|
+
after_transition(to: [:connecting], from: [:failed]) do |connection|
|
40
|
+
connection.manager.reintialize_failed_chanels
|
41
|
+
end
|
42
|
+
|
39
43
|
after_transition(to: [:connecting], from: [:disconnected, :suspended]) do |connection|
|
40
44
|
connection.manager.reconnect_transport
|
41
45
|
end
|
@@ -292,7 +292,7 @@ module Ably
|
|
292
292
|
def internet_up?
|
293
293
|
url = "http#{'s' if client.use_tls?}:#{Ably::INTERNET_CHECK.fetch(:url)}"
|
294
294
|
EventMachine::DefaultDeferrable.new.tap do |deferrable|
|
295
|
-
EventMachine::HttpRequest.new(url).get.tap do |http|
|
295
|
+
EventMachine::HttpRequest.new(url, tls: { verify_peer: true }).get.tap do |http|
|
296
296
|
http.errback do
|
297
297
|
yield false if block_given?
|
298
298
|
deferrable.fail Ably::Exceptions::ConnectionFailed.new("Unable to connect to #{url}", nil, Ably::Exceptions::Codes::CONNECTION_FAILED)
|
@@ -434,7 +434,7 @@ module Ably
|
|
434
434
|
'format' => client.protocol,
|
435
435
|
'echo' => client.echo_messages,
|
436
436
|
'v' => Ably::PROTOCOL_VERSION,
|
437
|
-
'
|
437
|
+
'agent' => client.rest_client.agent
|
438
438
|
)
|
439
439
|
|
440
440
|
# Use native websocket heartbeats if possible, but allow Ably protocol heartbeats
|
data/lib/ably/rest/channel.rb
CHANGED
@@ -52,8 +52,8 @@ module Ably
|
|
52
52
|
#
|
53
53
|
# # Publish an array of message Hashes
|
54
54
|
# messages = [
|
55
|
-
# { name: 'click', { x: 1, y: 2 } },
|
56
|
-
# { name: 'click', { x: 2, y: 3 } }
|
55
|
+
# { name: 'click', data: { x: 1, y: 2 } },
|
56
|
+
# { name: 'click', data: { x: 2, y: 3 } }
|
57
57
|
# ]
|
58
58
|
# channel.publish messages
|
59
59
|
#
|
@@ -85,7 +85,13 @@ module Ably
|
|
85
85
|
[[{ name: first, data: second }.merge(third)], nil]
|
86
86
|
end
|
87
87
|
|
88
|
-
|
88
|
+
messages.map! { |message| Ably::Models::Message(message.dup) }
|
89
|
+
|
90
|
+
if messages.sum(&:size) > (max_message_size = client.max_message_size || Ably::Rest::Client::MAX_MESSAGE_SIZE)
|
91
|
+
raise Ably::Exceptions::MaxMessageSizeExceeded.new("Maximum message size exceeded #{max_message_size} bytes.")
|
92
|
+
end
|
93
|
+
|
94
|
+
payload = messages.map do |message|
|
89
95
|
Ably::Models::Message(message.dup).tap do |msg|
|
90
96
|
msg.encode client.encoders, options
|
91
97
|
|
@@ -161,6 +167,8 @@ module Ably
|
|
161
167
|
def update_options(channel_options)
|
162
168
|
@options = channel_options.clone.freeze
|
163
169
|
end
|
170
|
+
alias set_options update_options # (RSL7)
|
171
|
+
alias options= update_options
|
164
172
|
|
165
173
|
private
|
166
174
|
def base_path
|
data/lib/ably/rest/client.rb
CHANGED
@@ -3,6 +3,9 @@ require 'json'
|
|
3
3
|
require 'logger'
|
4
4
|
require 'uri'
|
5
5
|
|
6
|
+
require 'typhoeus'
|
7
|
+
require 'typhoeus/adapters/faraday'
|
8
|
+
|
6
9
|
require 'ably/rest/middleware/exceptions'
|
7
10
|
|
8
11
|
module Ably
|
@@ -22,6 +25,9 @@ module Ably
|
|
22
25
|
# Default Ably domain for REST
|
23
26
|
DOMAIN = 'rest.ably.io'
|
24
27
|
|
28
|
+
MAX_MESSAGE_SIZE = 65536 # See spec TO3l8
|
29
|
+
MAX_FRAME_SIZE = 524288 # See spec TO3l8
|
30
|
+
|
25
31
|
# Configuration for HTTP timeouts and HTTP request reattempts to fallback hosts
|
26
32
|
HTTP_DEFAULTS = {
|
27
33
|
open_timeout: 4,
|
@@ -49,6 +55,10 @@ module Ably
|
|
49
55
|
# @return [Symbol]
|
50
56
|
attr_reader :protocol
|
51
57
|
|
58
|
+
# Client agent i.e. `example-gem/1.2.0 ably-ruby/1.1.5 ruby/1.9.3`
|
59
|
+
# @return [String]
|
60
|
+
attr_reader :agent
|
61
|
+
|
52
62
|
# {Ably::Auth} authentication object configured for this connection
|
53
63
|
# @return [Ably::Auth]
|
54
64
|
attr_reader :auth
|
@@ -109,6 +119,14 @@ module Ably
|
|
109
119
|
# @return [Boolean]
|
110
120
|
attr_reader :idempotent_rest_publishing
|
111
121
|
|
122
|
+
# Max message size (TO2, TO3l8) by default (65536 bytes) 64KiB
|
123
|
+
# @return [Integer]
|
124
|
+
attr_reader :max_message_size
|
125
|
+
|
126
|
+
# Max frame size (TO2, TO3l8) by default (524288 bytes) 512KiB
|
127
|
+
# @return [Integer]
|
128
|
+
attr_reader :max_frame_size
|
129
|
+
|
112
130
|
# Creates a {Ably::Rest::Client Rest Client} and configures the {Ably::Auth} object for the connection.
|
113
131
|
#
|
114
132
|
# @param [Hash,String] options an options Hash used to configure the client and the authentication, or String with an API key or Token ID
|
@@ -143,6 +161,8 @@ module Ably
|
|
143
161
|
#
|
144
162
|
# @option options [Boolean] :add_request_ids (false) When true, adds a unique request_id to each request sent to Ably servers. This is handy when reporting issues, because you can refer to a specific request.
|
145
163
|
# @option options [Boolean] :idempotent_rest_publishing (false if ver < 1.2) When true, idempotent publishing is enabled for all messages published via REST
|
164
|
+
# @option options [Integer] :max_message_size (65536 bytes) Maximum size of all messages when publishing via REST publish()
|
165
|
+
# @option options [Integer] :max_frame_size (524288 bytes) Maximum size of frame
|
146
166
|
#
|
147
167
|
# @return [Ably::Rest::Client]
|
148
168
|
#
|
@@ -165,6 +185,7 @@ module Ably
|
|
165
185
|
end
|
166
186
|
end
|
167
187
|
|
188
|
+
@agent = options.delete(:agent) || Ably::AGENT
|
168
189
|
@realtime_client = options.delete(:realtime_client)
|
169
190
|
@tls = options.delete(:tls) == false ? false : true
|
170
191
|
@environment = options.delete(:environment) # nil is production
|
@@ -179,18 +200,21 @@ module Ably
|
|
179
200
|
@add_request_ids = options.delete(:add_request_ids)
|
180
201
|
@log_retries_as_info = options.delete(:log_retries_as_info)
|
181
202
|
@idempotent_rest_publishing = options.delete(:idempotent_rest_publishing) || Ably.major_minor_version_numeric > 1.1
|
203
|
+
@max_message_size = options.delete(:max_message_size) || MAX_MESSAGE_SIZE
|
204
|
+
@max_frame_size = options.delete(:max_frame_size) || MAX_FRAME_SIZE
|
182
205
|
|
183
|
-
|
184
|
-
|
185
|
-
raise ArgumentError, "fallback_hosts_use_default cannot be set to trye when fallback_jhosts is also provided"
|
206
|
+
if options[:fallback_hosts_use_default] && options[:fallback_hosts]
|
207
|
+
raise ArgumentError, "fallback_hosts_use_default cannot be set to try when fallback_hosts is also provided"
|
186
208
|
end
|
187
209
|
@fallback_hosts = case
|
188
210
|
when options.delete(:fallback_hosts_use_default)
|
189
211
|
Ably::FALLBACK_HOSTS
|
190
212
|
when options_fallback_hosts = options.delete(:fallback_hosts)
|
191
213
|
options_fallback_hosts
|
192
|
-
when
|
214
|
+
when custom_host || options[:realtime_host] || custom_port || custom_tls_port
|
193
215
|
[]
|
216
|
+
when environment
|
217
|
+
CUSTOM_ENVIRONMENT_FALLBACKS_SUFFIXES.map { |host| "#{environment}#{host}" }
|
194
218
|
else
|
195
219
|
Ably::FALLBACK_HOSTS
|
196
220
|
end
|
@@ -202,6 +226,8 @@ module Ably
|
|
202
226
|
@http_defaults = HTTP_DEFAULTS.dup
|
203
227
|
options.each do |key, val|
|
204
228
|
if http_key = key[/^http_(.+)/, 1]
|
229
|
+
# Typhoeus converts decimal durations to milliseconds, so 0.0001 timeout is treated as 0 (no timeout)
|
230
|
+
val = 0.001 if val.kind_of?(Numeric) && (val > 0) && (val < 0.001)
|
205
231
|
@http_defaults[http_key.to_sym] = val if val && @http_defaults.has_key?(http_key.to_sym)
|
206
232
|
end
|
207
233
|
end
|
@@ -351,6 +377,9 @@ module Ably
|
|
351
377
|
send_request(method, path, params, headers: headers)
|
352
378
|
end
|
353
379
|
when :post, :patch, :put
|
380
|
+
if body.to_json.bytesize > max_frame_size
|
381
|
+
raise Ably::Exceptions::MaxFrameSizeExceeded.new("Maximum frame size exceeded #{max_frame_size} bytes.")
|
382
|
+
end
|
354
383
|
path_with_params = Addressable::URI.new
|
355
384
|
path_with_params.query_values = params || {}
|
356
385
|
query = path_with_params.query
|
@@ -466,16 +495,6 @@ module Ably
|
|
466
495
|
end
|
467
496
|
end
|
468
497
|
|
469
|
-
# Library Ably version user agent
|
470
|
-
# @api private
|
471
|
-
def lib_version_id
|
472
|
-
@lib_version_id ||= [
|
473
|
-
'ruby',
|
474
|
-
Ably.lib_variant,
|
475
|
-
Ably::VERSION
|
476
|
-
].compact.join('-')
|
477
|
-
end
|
478
|
-
|
479
498
|
# Allowable duration for an external auth request
|
480
499
|
# For REST client this defaults to request_timeout
|
481
500
|
# For Realtime clients this defaults to 250ms less than the realtime_request_timeout
|
@@ -656,7 +675,7 @@ module Ably
|
|
656
675
|
accept: mime_type,
|
657
676
|
user_agent: user_agent,
|
658
677
|
'X-Ably-Version' => Ably::PROTOCOL_VERSION,
|
659
|
-
'
|
678
|
+
'Ably-Agent' => agent
|
660
679
|
},
|
661
680
|
request: {
|
662
681
|
open_timeout: http_defaults.fetch(:open_timeout),
|
@@ -665,7 +684,7 @@ module Ably
|
|
665
684
|
}
|
666
685
|
end
|
667
686
|
|
668
|
-
# Return a Faraday middleware stack to initiate the Faraday::
|
687
|
+
# Return a Faraday middleware stack to initiate the Faraday::RackBuilder with
|
669
688
|
#
|
670
689
|
# @see http://mislav.uniqpath.com/2011/07/faraday-advanced-http/
|
671
690
|
def middleware
|
@@ -677,8 +696,8 @@ module Ably
|
|
677
696
|
|
678
697
|
setup_incoming_middleware builder, logger, fail_if_unsupported_mime_type: true
|
679
698
|
|
680
|
-
# Set Faraday's HTTP adapter
|
681
|
-
builder.adapter :
|
699
|
+
# Set Faraday's HTTP adapter with support for HTTP/2
|
700
|
+
builder.adapter :typhoeus, http_version: :httpv2_0
|
682
701
|
end
|
683
702
|
end
|
684
703
|
|
@@ -7,7 +7,10 @@ module Ably
|
|
7
7
|
class FailIfUnsupportedMimeType < Faraday::Response::Middleware
|
8
8
|
def on_complete(env)
|
9
9
|
unless env.response_headers['Ably-Middleware-Parsed'] == true
|
10
|
-
|
10
|
+
# Ignore empty body with success status code for no body response
|
11
|
+
return if env.body.to_s.empty? && env.status == 204
|
12
|
+
|
13
|
+
unless (500..599).include?(env.status)
|
11
14
|
raise Ably::Exceptions::InvalidResponseBody,
|
12
15
|
"Content Type #{env.response_headers['Content-Type']} is not supported by this client library"
|
13
16
|
end
|