ably-rest 1.0.6 → 1.1.4.rc
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/CHANGELOG.md +1 -1
- data/README.md +23 -15
- data/ably-rest.gemspec +6 -6
- data/lib/submodules/ably-ruby/.editorconfig +14 -0
- data/lib/submodules/ably-ruby/.travis.yml +10 -8
- data/lib/submodules/ably-ruby/CHANGELOG.md +75 -3
- data/lib/submodules/ably-ruby/LICENSE +1 -3
- data/lib/submodules/ably-ruby/README.md +12 -7
- data/lib/submodules/ably-ruby/Rakefile +32 -0
- data/lib/submodules/ably-ruby/SPEC.md +1277 -835
- data/lib/submodules/ably-ruby/ably.gemspec +15 -10
- data/lib/submodules/ably-ruby/lib/ably/auth.rb +30 -4
- data/lib/submodules/ably-ruby/lib/ably/exceptions.rb +10 -4
- data/lib/submodules/ably-ruby/lib/ably/logger.rb +7 -1
- data/lib/submodules/ably-ruby/lib/ably/models/channel_state_change.rb +1 -1
- data/lib/submodules/ably-ruby/lib/ably/models/connection_state_change.rb +1 -1
- data/lib/submodules/ably-ruby/lib/ably/models/device_details.rb +87 -0
- data/lib/submodules/ably-ruby/lib/ably/models/device_push_details.rb +86 -0
- data/lib/submodules/ably-ruby/lib/ably/models/error_info.rb +23 -2
- data/lib/submodules/ably-ruby/lib/ably/models/idiomatic_ruby_wrapper.rb +4 -4
- data/lib/submodules/ably-ruby/lib/ably/models/protocol_message.rb +32 -2
- data/lib/submodules/ably-ruby/lib/ably/models/push_channel_subscription.rb +89 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/conversions.rb +1 -1
- data/lib/submodules/ably-ruby/lib/ably/modules/encodeable.rb +1 -1
- data/lib/submodules/ably-ruby/lib/ably/modules/exception_codes.rb +128 -0
- data/lib/submodules/ably-ruby/lib/ably/modules/model_common.rb +15 -2
- data/lib/submodules/ably-ruby/lib/ably/modules/state_machine.rb +2 -2
- data/lib/submodules/ably-ruby/lib/ably/realtime.rb +1 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/auth.rb +1 -1
- data/lib/submodules/ably-ruby/lib/ably/realtime/channel.rb +24 -102
- data/lib/submodules/ably-ruby/lib/ably/realtime/channel/channel_manager.rb +2 -6
- data/lib/submodules/ably-ruby/lib/ably/realtime/channel/channel_state_machine.rb +2 -2
- data/lib/submodules/ably-ruby/lib/ably/realtime/channel/publisher.rb +74 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/channel/push_channel.rb +62 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/client.rb +91 -3
- data/lib/submodules/ably-ruby/lib/ably/realtime/client/incoming_message_dispatcher.rb +6 -2
- data/lib/submodules/ably-ruby/lib/ably/realtime/client/outgoing_message_dispatcher.rb +1 -1
- data/lib/submodules/ably-ruby/lib/ably/realtime/connection.rb +34 -20
- data/lib/submodules/ably-ruby/lib/ably/realtime/connection/connection_manager.rb +25 -9
- data/lib/submodules/ably-ruby/lib/ably/realtime/connection/websocket_transport.rb +1 -1
- data/lib/submodules/ably-ruby/lib/ably/realtime/presence.rb +4 -4
- data/lib/submodules/ably-ruby/lib/ably/realtime/presence/members_map.rb +3 -3
- data/lib/submodules/ably-ruby/lib/ably/realtime/push.rb +40 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/push/admin.rb +61 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/push/channel_subscriptions.rb +108 -0
- data/lib/submodules/ably-ruby/lib/ably/realtime/push/device_registrations.rb +105 -0
- data/lib/submodules/ably-ruby/lib/ably/rest.rb +1 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/channel.rb +53 -17
- data/lib/submodules/ably-ruby/lib/ably/rest/channel/push_channel.rb +62 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/client.rb +162 -35
- data/lib/submodules/ably-ruby/lib/ably/rest/middleware/fail_if_unsupported_mime_type.rb +4 -1
- data/lib/submodules/ably-ruby/lib/ably/rest/middleware/parse_message_pack.rb +17 -1
- data/lib/submodules/ably-ruby/lib/ably/rest/presence.rb +1 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/push.rb +42 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/push/admin.rb +54 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/push/channel_subscriptions.rb +121 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/push/device_registrations.rb +103 -0
- data/lib/submodules/ably-ruby/lib/ably/version.rb +7 -2
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/auth_spec.rb +245 -17
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_history_spec.rb +26 -20
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_spec.rb +177 -59
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/client_spec.rb +153 -0
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_failures_spec.rb +72 -6
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_spec.rb +129 -18
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/message_spec.rb +36 -34
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/presence_spec.rb +201 -167
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/push_admin_spec.rb +736 -0
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/push_spec.rb +27 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/auth_spec.rb +41 -3
- data/lib/submodules/ably-ruby/spec/acceptance/rest/base_spec.rb +2 -2
- data/lib/submodules/ably-ruby/spec/acceptance/rest/channel_spec.rb +79 -4
- data/lib/submodules/ably-ruby/spec/acceptance/rest/channels_spec.rb +6 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/client_spec.rb +129 -10
- data/lib/submodules/ably-ruby/spec/acceptance/rest/message_spec.rb +158 -6
- data/lib/submodules/ably-ruby/spec/acceptance/rest/push_admin_spec.rb +952 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/push_spec.rb +25 -0
- data/lib/submodules/ably-ruby/spec/acceptance/rest/time_spec.rb +1 -1
- data/lib/submodules/ably-ruby/spec/run_parallel_tests +33 -0
- data/lib/submodules/ably-ruby/spec/spec_helper.rb +1 -1
- data/lib/submodules/ably-ruby/spec/support/debug_failure_helper.rb +9 -5
- data/lib/submodules/ably-ruby/spec/support/test_app.rb +2 -2
- data/lib/submodules/ably-ruby/spec/unit/logger_spec.rb +10 -3
- data/lib/submodules/ably-ruby/spec/unit/models/device_details_spec.rb +102 -0
- data/lib/submodules/ably-ruby/spec/unit/models/device_push_details_spec.rb +101 -0
- data/lib/submodules/ably-ruby/spec/unit/models/error_info_spec.rb +51 -3
- data/lib/submodules/ably-ruby/spec/unit/models/message_spec.rb +17 -2
- data/lib/submodules/ably-ruby/spec/unit/models/presence_message_spec.rb +1 -1
- data/lib/submodules/ably-ruby/spec/unit/models/push_channel_subscription_spec.rb +86 -0
- data/lib/submodules/ably-ruby/spec/unit/modules/enum_spec.rb +1 -1
- data/lib/submodules/ably-ruby/spec/unit/realtime/client_spec.rb +13 -1
- data/lib/submodules/ably-ruby/spec/unit/realtime/connection_spec.rb +1 -1
- data/lib/submodules/ably-ruby/spec/unit/realtime/push_channel_spec.rb +36 -0
- data/lib/submodules/ably-ruby/spec/unit/rest/channel_spec.rb +8 -1
- data/lib/submodules/ably-ruby/spec/unit/rest/client_spec.rb +30 -0
- data/lib/submodules/ably-ruby/spec/unit/rest/push_channel_spec.rb +36 -0
- metadata +46 -21
@@ -3,8 +3,6 @@ module Ably
|
|
3
3
|
# The Ably Realtime service organises the traffic within any application into named channels.
|
4
4
|
# Channels are the "unit" of message distribution; clients attach to channels to subscribe to messages, and every message broadcast by the service is associated with a unique channel.
|
5
5
|
#
|
6
|
-
# @!attribute [r] client
|
7
|
-
# @return {Ably::Realtime::Client} Ably client associated with this channel
|
8
6
|
# @!attribute [r] name
|
9
7
|
# @return {String} channel name
|
10
8
|
# @!attribute [r] options
|
@@ -12,7 +10,19 @@ module Ably
|
|
12
10
|
class Channel
|
13
11
|
include Ably::Modules::Conversions
|
14
12
|
|
15
|
-
|
13
|
+
# Ably client associated with this channel
|
14
|
+
# @return [Ably::Realtime::Client]
|
15
|
+
# @api private
|
16
|
+
attr_reader :client
|
17
|
+
|
18
|
+
attr_reader :name, :options
|
19
|
+
|
20
|
+
# Push channel used for push notification (client-side)
|
21
|
+
# @return [Ably::Rest::Channel::PushChannel]
|
22
|
+
# @api private
|
23
|
+
attr_reader :push
|
24
|
+
|
25
|
+
IDEMPOTENT_LIBRARY_GENERATED_ID_LENGTH = 9 # See spec RSL1k1
|
16
26
|
|
17
27
|
# Initialize a new Channel object
|
18
28
|
#
|
@@ -27,17 +37,17 @@ module Ably
|
|
27
37
|
update_options channel_options
|
28
38
|
@client = client
|
29
39
|
@name = name
|
40
|
+
@push = PushChannel.new(self)
|
30
41
|
end
|
31
42
|
|
32
|
-
# Publish one or more messages to the channel.
|
33
|
-
#
|
34
|
-
# @param
|
35
|
-
# @param
|
36
|
-
# @param attributes [Hash, nil] Optional additional message attributes such as :client_id or :connection_id, applied when name attribute is nil or a string
|
43
|
+
# Publish one or more messages to the channel. Three overloaded forms
|
44
|
+
# @param name [String, Array<Ably::Models::Message|Hash>, Ably::Models::Message, nil] The event name of the message to publish, or an Array of [Ably::Model::Message] objects or [Hash] objects with +:name+ and +:data+ pairs, or a single Ably::Model::Message object
|
45
|
+
# @param data [String, ByteArray, Hash, nil] The message payload unless an Array of [Ably::Model::Message] objects passed in the first argument, in which case an optional hash of query parameters
|
46
|
+
# @param attributes [Hash, nil] Optional additional message attributes such as :extras, :id, :client_id or :connection_id, applied when name attribute is nil or a string (Deprecated, will be removed in 2.0 in favour of constructing a Message object)
|
37
47
|
# @return [Boolean] true if the message was published, otherwise false
|
38
48
|
#
|
39
49
|
# @example
|
40
|
-
# # Publish a single message
|
50
|
+
# # Publish a single message with (name, data) form
|
41
51
|
# channel.publish 'click', { x: 1, y: 2 }
|
42
52
|
#
|
43
53
|
# # Publish an array of message Hashes
|
@@ -54,16 +64,28 @@ module Ably
|
|
54
64
|
# ]
|
55
65
|
# channel.publish messages
|
56
66
|
#
|
57
|
-
|
58
|
-
|
59
|
-
|
67
|
+
# # Publish a single Ably::Models::Message object, with a query params
|
68
|
+
# # specifying quickAck: true
|
69
|
+
# message = Ably::Models::Message(name: 'click', { x: 1, y: 2 })
|
70
|
+
# channel.publish message, quickAck: 'true'
|
71
|
+
#
|
72
|
+
def publish(first, second = nil, third = {})
|
73
|
+
messages, qs_params = if first.kind_of?(Enumerable)
|
74
|
+
# ([Message], qs_params) form
|
75
|
+
[first, second]
|
76
|
+
elsif first.kind_of?(Ably::Models::Message)
|
77
|
+
# (Message, qs_params) form
|
78
|
+
[[first], second]
|
60
79
|
else
|
61
|
-
|
62
|
-
|
63
|
-
|
80
|
+
# (name, data, attributes) form
|
81
|
+
first = ensure_utf_8(:name, first, allow_nil: true)
|
82
|
+
ensure_supported_payload second
|
83
|
+
# RSL1h - attributes as an extra method parameter is extra-spec but need to
|
84
|
+
# keep it for backcompat until version 2
|
85
|
+
[[{ name: first, data: second }.merge(third)], nil]
|
64
86
|
end
|
65
87
|
|
66
|
-
payload = messages.map do |message|
|
88
|
+
payload = messages.each_with_index.map do |message, index|
|
67
89
|
Ably::Models::Message(message.dup).tap do |msg|
|
68
90
|
msg.encode client.encoders, options
|
69
91
|
|
@@ -75,9 +97,21 @@ module Ably
|
|
75
97
|
raise Ably::Exceptions::IncompatibleClientId.new("Cannot publish with client_id '#{msg.client_id}' as it is incompatible with the current configured client_id '#{client.client_id}'")
|
76
98
|
end
|
77
99
|
end.as_json
|
100
|
+
end.tap do |payload|
|
101
|
+
if client.idempotent_rest_publishing
|
102
|
+
# We cannot mutate for idempotent publishing if one or more messages already has an ID
|
103
|
+
if payload.all? { |msg| !msg['id'] }
|
104
|
+
# Mutate the JSON to support idempotent publishing where a Message.id does not exist
|
105
|
+
idempotent_publish_id = SecureRandom.base64(IDEMPOTENT_LIBRARY_GENERATED_ID_LENGTH)
|
106
|
+
payload.each_with_index do |msg, idx|
|
107
|
+
msg['id'] = "#{idempotent_publish_id}:#{idx}"
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
78
111
|
end
|
79
112
|
|
80
|
-
|
113
|
+
options = qs_params ? { qs_params: qs_params } : {}
|
114
|
+
response = client.post("#{base_path}/publish", payload.length == 1 ? payload.first : payload, options)
|
81
115
|
|
82
116
|
[201, 204].include?(response.status)
|
83
117
|
end
|
@@ -141,3 +175,5 @@ module Ably
|
|
141
175
|
end
|
142
176
|
end
|
143
177
|
end
|
178
|
+
|
179
|
+
require 'ably/rest/channel/push_channel'
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module Ably::Rest
|
2
|
+
class Channel
|
3
|
+
# A push channel used for push notifications
|
4
|
+
# Each PushChannel maps to exactly one Rest Channel
|
5
|
+
#
|
6
|
+
# @!attribute [r] channel
|
7
|
+
# @return [Ably::Rest::Channel] Underlying channel object
|
8
|
+
#
|
9
|
+
class PushChannel
|
10
|
+
attr_reader :channel
|
11
|
+
|
12
|
+
def initialize(channel)
|
13
|
+
raise ArgumentError, "Unsupported channel type '#{channel.class}'" unless channel.kind_of?(Ably::Rest::Channel)
|
14
|
+
@channel = channel
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_s
|
18
|
+
"<PushChannel: name=#{channel.name}>"
|
19
|
+
end
|
20
|
+
|
21
|
+
# Subscribe local device for push notifications on this channel
|
22
|
+
#
|
23
|
+
# @note This is unsupported in the Ruby library
|
24
|
+
def subscribe_device(*args)
|
25
|
+
raise_unsupported
|
26
|
+
end
|
27
|
+
|
28
|
+
# Subscribe all devices registered to this client's authenticated client_id for push notifications on this channel
|
29
|
+
#
|
30
|
+
# @note This is unsupported in the Ruby library
|
31
|
+
def subscribe_client_id(*args)
|
32
|
+
raise_unsupported
|
33
|
+
end
|
34
|
+
|
35
|
+
# Unsubscribe local device for push notifications on this channel
|
36
|
+
#
|
37
|
+
# @note This is unsupported in the Ruby library
|
38
|
+
def unsubscribe_device(*args)
|
39
|
+
raise_unsupported
|
40
|
+
end
|
41
|
+
|
42
|
+
# Unsubscribe all devices registered to this client's authenticated client_id for push notifications on this channel
|
43
|
+
#
|
44
|
+
# @note This is unsupported in the Ruby library
|
45
|
+
def unsubscribe_client_id(*args)
|
46
|
+
raise_unsupported
|
47
|
+
end
|
48
|
+
|
49
|
+
# Get list of subscriptions on this channel for this device or authenticate client_id
|
50
|
+
#
|
51
|
+
# @note This is unsupported in the Ruby library
|
52
|
+
def get_subscriptions(*args)
|
53
|
+
raise_unsupported
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
def raise_unsupported
|
58
|
+
raise Ably::Exceptions::PushNotificationsNotSupported, 'This device does not support receiving or subscribing to push notifications. All PushChannel methods are unavailable'
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -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
|
@@ -30,6 +33,15 @@ module Ably
|
|
30
33
|
max_retry_count: 3
|
31
34
|
}.freeze
|
32
35
|
|
36
|
+
FALLBACK_RETRY_TIMEOUT = 10 * 60
|
37
|
+
|
38
|
+
# Faraday 1.0 introduced new error types, however we want to support Faraday <1 too which only used Faraday::ClientError
|
39
|
+
FARADAY_CLIENT_OR_SERVER_ERRORS = if defined?(Faraday::ParsingError)
|
40
|
+
[Faraday::ClientError, Faraday::ServerError, Faraday::ConnectionFailed, Faraday::SSLError, Faraday::ParsingError]
|
41
|
+
else
|
42
|
+
Faraday::ClientError
|
43
|
+
end
|
44
|
+
|
33
45
|
def_delegators :auth, :client_id, :auth_options
|
34
46
|
|
35
47
|
# Custom environment to use such as 'sandbox' when testing the client library against an alternate Ably environment
|
@@ -83,10 +95,23 @@ module Ably
|
|
83
95
|
# if empty or nil then fallback host functionality is disabled
|
84
96
|
attr_reader :fallback_hosts
|
85
97
|
|
86
|
-
#
|
98
|
+
# Whether the {Client} has to add a random identifier to the path of a request
|
87
99
|
# @return [Boolean]
|
88
100
|
attr_reader :add_request_ids
|
89
101
|
|
102
|
+
# Retries are logged by default to warn and error. When true, retries are logged at info level
|
103
|
+
# @return [Boolean]
|
104
|
+
# @api private
|
105
|
+
attr_reader :log_retries_as_info
|
106
|
+
|
107
|
+
# True when idempotent publishing is enabled for all messages published via REST.
|
108
|
+
# When this feature is enabled, the client library will add a unique ID to every published message (without an ID)
|
109
|
+
# ensuring any failed published attempts (due to failures such as HTTP requests failing mid-flight) that are
|
110
|
+
# automatically retried will not result in duplicate messages being published to the Ably platform.
|
111
|
+
# Note: This is a beta unsupported feature!
|
112
|
+
# @return [Boolean]
|
113
|
+
attr_reader :idempotent_rest_publishing
|
114
|
+
|
90
115
|
# Creates a {Ably::Rest::Client Rest Client} and configures the {Ably::Auth} object for the connection.
|
91
116
|
#
|
92
117
|
# @param [Hash,String] options an options Hash used to configure the client and the authentication, or String with an API key or Token ID
|
@@ -117,6 +142,10 @@ module Ably
|
|
117
142
|
#
|
118
143
|
# @option options [Boolean] :fallback_hosts_use_default (false) When true, forces the user of fallback hosts even if a non-default production endpoint is being used
|
119
144
|
# @option options [Array<String>] :fallback_hosts When an array of fallback hosts are provided, these fallback hosts are always used if a request fails to the primary endpoint. If an empty array is provided, the fallback host functionality is disabled
|
145
|
+
# @option options [Integer] :fallback_retry_timeout (600 seconds) amount of time in seconds a REST client will continue to use a working fallback host when the primary fallback host has previously failed
|
146
|
+
#
|
147
|
+
# @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.
|
148
|
+
# @option options [Boolean] :idempotent_rest_publishing (false if ver < 1.2) When true, idempotent publishing is enabled for all messages published via REST
|
120
149
|
#
|
121
150
|
# @return [Ably::Rest::Client]
|
122
151
|
#
|
@@ -139,18 +168,21 @@ module Ably
|
|
139
168
|
end
|
140
169
|
end
|
141
170
|
|
142
|
-
@realtime_client
|
143
|
-
@tls
|
144
|
-
@environment
|
145
|
-
@environment
|
146
|
-
@protocol
|
147
|
-
@debug_http
|
148
|
-
@log_level
|
149
|
-
@custom_logger
|
150
|
-
@custom_host
|
151
|
-
@custom_port
|
152
|
-
@custom_tls_port
|
153
|
-
@add_request_ids
|
171
|
+
@realtime_client = options.delete(:realtime_client)
|
172
|
+
@tls = options.delete(:tls) == false ? false : true
|
173
|
+
@environment = options.delete(:environment) # nil is production
|
174
|
+
@environment = nil if [:production, 'production'].include?(@environment)
|
175
|
+
@protocol = options.delete(:protocol) || :msgpack
|
176
|
+
@debug_http = options.delete(:debug_http)
|
177
|
+
@log_level = options.delete(:log_level) || ::Logger::WARN
|
178
|
+
@custom_logger = options.delete(:logger)
|
179
|
+
@custom_host = options.delete(:rest_host)
|
180
|
+
@custom_port = options.delete(:port)
|
181
|
+
@custom_tls_port = options.delete(:tls_port)
|
182
|
+
@add_request_ids = options.delete(:add_request_ids)
|
183
|
+
@log_retries_as_info = options.delete(:log_retries_as_info)
|
184
|
+
@idempotent_rest_publishing = options.delete(:idempotent_rest_publishing) || Ably.major_minor_version_numeric > 1.1
|
185
|
+
|
154
186
|
|
155
187
|
if options[:fallback_hosts_use_default] && options[:fallback_jhosts]
|
156
188
|
raise ArgumentError, "fallback_hosts_use_default cannot be set to trye when fallback_jhosts is also provided"
|
@@ -166,9 +198,15 @@ module Ably
|
|
166
198
|
Ably::FALLBACK_HOSTS
|
167
199
|
end
|
168
200
|
|
201
|
+
options[:fallback_retry_timeout] ||= FALLBACK_RETRY_TIMEOUT
|
202
|
+
|
203
|
+
# Take option keys prefixed with `http_`, remove the http_ and
|
204
|
+
# check if the option exists in HTTP_DEFAULTS. If so, update http_defaults
|
169
205
|
@http_defaults = HTTP_DEFAULTS.dup
|
170
206
|
options.each do |key, val|
|
171
207
|
if http_key = key[/^http_(.+)/, 1]
|
208
|
+
# Typhoeus converts decimal durations to milliseconds, so 0.0001 timeout is treated as 0 (no timeout)
|
209
|
+
val = 0.001 if val.kind_of?(Numeric) && (val > 0) && (val < 0.001)
|
172
210
|
@http_defaults[http_key.to_sym] = val if val && @http_defaults.has_key?(http_key.to_sym)
|
173
211
|
end
|
174
212
|
end
|
@@ -190,8 +228,12 @@ module Ably
|
|
190
228
|
raise ArgumentError, 'Protocol is invalid. Must be either :msgpack or :json' unless [:msgpack, :json].include?(@protocol)
|
191
229
|
|
192
230
|
token_params = options.delete(:default_token_params) || {}
|
193
|
-
@options
|
194
|
-
|
231
|
+
@options = options
|
232
|
+
init_auth_options = options.select do |key, _|
|
233
|
+
Auth::AUTH_OPTIONS_KEYS.include?(key.to_s)
|
234
|
+
end
|
235
|
+
|
236
|
+
@auth = Auth.new(self, token_params, init_auth_options)
|
195
237
|
@channels = Ably::Rest::Channels.new(self)
|
196
238
|
@encoders = []
|
197
239
|
|
@@ -273,6 +315,24 @@ module Ably
|
|
273
315
|
raw_request(:post, path, params, options)
|
274
316
|
end
|
275
317
|
|
318
|
+
# Perform an HTTP PUT request to the API using configured authentication
|
319
|
+
#
|
320
|
+
# @return [Faraday::Response]
|
321
|
+
#
|
322
|
+
# @api private
|
323
|
+
def put(path, params, options = {})
|
324
|
+
raw_request(:put, path, params, options)
|
325
|
+
end
|
326
|
+
|
327
|
+
# Perform an HTTP DELETE request to the API using configured authentication
|
328
|
+
#
|
329
|
+
# @return [Faraday::Response]
|
330
|
+
#
|
331
|
+
# @api private
|
332
|
+
def delete(path, params, options = {})
|
333
|
+
raw_request(:delete, path, params, options)
|
334
|
+
end
|
335
|
+
|
276
336
|
# Perform an HTTP request to the Ably API
|
277
337
|
# This is a convenience for customers who wish to use bleeding edge REST API functionality
|
278
338
|
# that is either not documented or is not included in the API for our client libraries.
|
@@ -292,14 +352,14 @@ module Ably
|
|
292
352
|
|
293
353
|
response = case method.to_sym
|
294
354
|
when :get
|
295
|
-
|
355
|
+
reauthorize_on_authorization_failure do
|
296
356
|
send_request(method, path, params, headers: headers)
|
297
357
|
end
|
298
358
|
when :post
|
299
359
|
path_with_params = Addressable::URI.new
|
300
360
|
path_with_params.query_values = params || {}
|
301
361
|
query = path_with_params.query
|
302
|
-
|
362
|
+
reauthorize_on_authorization_failure do
|
303
363
|
send_request(method, "#{path}#{"?#{query}" unless query.nil? || query.empty?}", body, headers: headers)
|
304
364
|
end
|
305
365
|
end
|
@@ -321,6 +381,20 @@ module Ably
|
|
321
381
|
Models::HttpPaginatedResponse.new(response, path, self)
|
322
382
|
end
|
323
383
|
|
384
|
+
# The local device detilas
|
385
|
+
# @return [Ably::Models::LocalDevice]
|
386
|
+
#
|
387
|
+
# @note This is unsupported in the Ruby library
|
388
|
+
def device
|
389
|
+
raise Ably::Exceptions::PushNotificationsNotSupported, 'This device does not support receiving or subscribing to push notifications. The local device object is not unavailable'
|
390
|
+
end
|
391
|
+
|
392
|
+
# Push notification object for publishing and managing push notifications
|
393
|
+
# @return [Ably::Rest::Push]
|
394
|
+
def push
|
395
|
+
@push ||= Push.new(self)
|
396
|
+
end
|
397
|
+
|
324
398
|
# @!attribute [r] endpoint
|
325
399
|
# @return [URI::Generic] Default Ably REST endpoint used for all requests
|
326
400
|
def endpoint
|
@@ -389,7 +463,6 @@ module Ably
|
|
389
463
|
def fallback_connection
|
390
464
|
unless defined?(@fallback_connections) && @fallback_connections
|
391
465
|
@fallback_connections = fallback_hosts.shuffle.map { |host| Faraday.new(endpoint_for_host(host).to_s, connection_options) }
|
392
|
-
@fallback_connections << Faraday.new(endpoint.to_s, connection_options) # Try the original host last if all fallbacks have been used
|
393
466
|
end
|
394
467
|
@fallback_index ||= 0
|
395
468
|
|
@@ -410,23 +483,58 @@ module Ably
|
|
410
483
|
|
411
484
|
# Allowable duration for an external auth request
|
412
485
|
# For REST client this defaults to request_timeout
|
413
|
-
# For Realtime clients this defaults to realtime_request_timeout
|
486
|
+
# For Realtime clients this defaults to 250ms less than the realtime_request_timeout
|
487
|
+
# ensuring an auth failure will be triggered before the realtime request timeout fires
|
488
|
+
# which would lead to a misleading error message (connection timeout as opposed to auth request timeout)
|
414
489
|
# @api private
|
415
490
|
def auth_request_timeout
|
416
491
|
if @realtime_client
|
417
|
-
@realtime_client.connection.defaults.fetch(:realtime_request_timeout)
|
492
|
+
@realtime_client.connection.defaults.fetch(:realtime_request_timeout) - 0.25
|
418
493
|
else
|
419
494
|
http_defaults.fetch(:request_timeout)
|
420
495
|
end
|
421
496
|
end
|
422
497
|
|
498
|
+
# If the primary host endpoint fails, and a subsequent fallback host succeeds, the fallback
|
499
|
+
# host that succeeded is used for +ClientOption+ +fallback_retry_timeout+ seconds to avoid
|
500
|
+
# retries to known failing hosts for a short period of time.
|
501
|
+
# See https://github.com/ably/docs/pull/554, spec id #RSC15f
|
502
|
+
#
|
503
|
+
# @return [nil, String] Returns nil (falsey) if the primary host is being used, or the currently used host if a fallback host is currently preferred
|
504
|
+
def using_preferred_fallback_host?
|
505
|
+
if preferred_fallback_connection && (preferred_fallback_connection.fetch(:expires_at) > Time.now)
|
506
|
+
preferred_fallback_connection.fetch(:connection_object).host
|
507
|
+
end
|
508
|
+
end
|
509
|
+
|
423
510
|
private
|
511
|
+
|
512
|
+
attr_reader :preferred_fallback_connection
|
513
|
+
|
514
|
+
# See #using_preferred_fallback_host? for context
|
515
|
+
def set_preferred_fallback_connection(connection)
|
516
|
+
@preferred_fallback_connection = if connection == @connection
|
517
|
+
# If the succeeded connection is in fact the primary connection (tried after a failed fallback)
|
518
|
+
# then clear the preferred fallback connection
|
519
|
+
nil
|
520
|
+
else
|
521
|
+
{
|
522
|
+
expires_at: Time.now + options.fetch(:fallback_retry_timeout),
|
523
|
+
connection_object: connection,
|
524
|
+
}
|
525
|
+
end
|
526
|
+
end
|
527
|
+
|
528
|
+
def get_preferred_fallback_connection_object
|
529
|
+
preferred_fallback_connection.fetch(:connection_object) if using_preferred_fallback_host?
|
530
|
+
end
|
531
|
+
|
424
532
|
def raw_request(method, path, params = {}, options = {})
|
425
533
|
options = options.clone
|
426
534
|
if options.delete(:disable_automatic_reauthorize) == true
|
427
535
|
send_request(method, path, params, options)
|
428
536
|
else
|
429
|
-
|
537
|
+
reauthorize_on_authorization_failure do
|
430
538
|
send_request(method, path, params, options)
|
431
539
|
end
|
432
540
|
end
|
@@ -442,15 +550,30 @@ module Ably
|
|
442
550
|
retry_sequence_id = nil
|
443
551
|
request_id = SecureRandom.urlsafe_base64(10) if add_request_ids
|
444
552
|
|
553
|
+
preferred_fallback_connection_for_first_request = get_preferred_fallback_connection_object
|
554
|
+
|
445
555
|
begin
|
446
|
-
use_fallback = can_fallback_to_alternate_ably_host? && retry_count > 0
|
556
|
+
use_fallback = can_fallback_to_alternate_ably_host? && (retry_count > 0)
|
557
|
+
|
558
|
+
conn = if preferred_fallback_connection_for_first_request
|
559
|
+
case retry_count
|
560
|
+
when 0
|
561
|
+
preferred_fallback_connection_for_first_request
|
562
|
+
when 1
|
563
|
+
# Ensure the root host is used first if the preferred fallback fails, see #RSC15f
|
564
|
+
connection(use_fallback: false)
|
565
|
+
end
|
566
|
+
end || connection(use_fallback: use_fallback) # default to normal connection selection process if not preferred connection set
|
447
567
|
|
448
|
-
|
568
|
+
conn.send(method, path, params) do |request|
|
449
569
|
if add_request_ids
|
450
570
|
request.params[:request_id] = request_id
|
451
571
|
request.options.context = {} if request.options.context.nil?
|
452
572
|
request.options.context[:request_id] = request_id
|
453
573
|
end
|
574
|
+
if options[:qs_params]
|
575
|
+
request.params.merge!(options[:qs_params])
|
576
|
+
end
|
454
577
|
unless options[:send_auth_header] == false
|
455
578
|
request.headers[:authorization] = auth.auth_header
|
456
579
|
if options[:headers]
|
@@ -461,41 +584,45 @@ module Ably
|
|
461
584
|
end
|
462
585
|
end.tap do
|
463
586
|
if retry_count > 0
|
464
|
-
|
587
|
+
retry_log_severity = log_retries_as_info ? :info : :warn
|
588
|
+
logger.public_send(retry_log_severity) do
|
465
589
|
"Ably::Rest::Client - Request SUCCEEDED after #{retry_count} #{retry_count > 1 ? 'retries' : 'retry' } for" \
|
466
590
|
" #{method} #{path} #{params} (seq ##{retry_sequence_id}, time elapsed #{(Time.now.to_f - requested_at.to_f).round(2)}s)"
|
467
591
|
end
|
592
|
+
set_preferred_fallback_connection conn
|
468
593
|
end
|
469
594
|
end
|
470
595
|
|
471
|
-
rescue Faraday::TimeoutError,
|
596
|
+
rescue *([Faraday::TimeoutError, Ably::Exceptions::ServerError] + FARADAY_CLIENT_OR_SERVER_ERRORS) => error
|
472
597
|
retry_sequence_id ||= SecureRandom.urlsafe_base64(4)
|
473
598
|
time_passed = Time.now - requested_at
|
474
599
|
|
475
|
-
if can_fallback_to_alternate_ably_host? && retry_count < max_retry_count && time_passed <= max_retry_duration
|
600
|
+
if can_fallback_to_alternate_ably_host? && (retry_count < max_retry_count) && (time_passed <= max_retry_duration)
|
476
601
|
retry_count += 1
|
477
|
-
|
602
|
+
retry_log_severity = log_retries_as_info ? :info : :warn
|
603
|
+
logger.public_send(retry_log_severity) { "Ably::Rest::Client - Retry #{retry_count} for #{method} #{path} #{params} as initial attempt failed (seq ##{retry_sequence_id}): #{error}" }
|
478
604
|
retry
|
479
605
|
end
|
480
606
|
|
481
|
-
|
607
|
+
retry_log_severity = log_retries_as_info ? :info : :error
|
608
|
+
logger.public_send(retry_log_severity) do
|
482
609
|
"Ably::Rest::Client - Request FAILED after #{retry_count} #{retry_count > 1 ? 'retries' : 'retry' } for" \
|
483
610
|
" #{method} #{path} #{params} (seq ##{retry_sequence_id}, time elapsed #{(Time.now.to_f - requested_at.to_f).round(2)}s)"
|
484
611
|
end
|
485
612
|
|
486
613
|
case error
|
487
614
|
when Faraday::TimeoutError
|
488
|
-
raise Ably::Exceptions::ConnectionTimeout.new(error.message, nil,
|
489
|
-
when
|
615
|
+
raise Ably::Exceptions::ConnectionTimeout.new(error.message, nil, Ably::Exceptions::Codes::CONNECTION_TIMED_OUT, error, { request_id: request_id })
|
616
|
+
when *FARADAY_CLIENT_OR_SERVER_ERRORS
|
490
617
|
# request_id is also available in the request context
|
491
|
-
raise Ably::Exceptions::ConnectionError.new(error.message, nil,
|
618
|
+
raise Ably::Exceptions::ConnectionError.new(error.message, nil, Ably::Exceptions::Codes::CONNECTION_FAILED, error, { request_id: request_id })
|
492
619
|
else
|
493
620
|
raise error
|
494
621
|
end
|
495
622
|
end
|
496
623
|
end
|
497
624
|
|
498
|
-
def
|
625
|
+
def reauthorize_on_authorization_failure
|
499
626
|
yield
|
500
627
|
rescue Ably::Exceptions::TokenExpired => e
|
501
628
|
if auth.token_renewable?
|
@@ -544,7 +671,7 @@ module Ably
|
|
544
671
|
}
|
545
672
|
end
|
546
673
|
|
547
|
-
# Return a Faraday middleware stack to initiate the Faraday::
|
674
|
+
# Return a Faraday middleware stack to initiate the Faraday::RackBuilder with
|
548
675
|
#
|
549
676
|
# @see http://mislav.uniqpath.com/2011/07/faraday-advanced-http/
|
550
677
|
def middleware
|
@@ -556,8 +683,8 @@ module Ably
|
|
556
683
|
|
557
684
|
setup_incoming_middleware builder, logger, fail_if_unsupported_mime_type: true
|
558
685
|
|
559
|
-
# Set Faraday's HTTP adapter
|
560
|
-
builder.adapter :
|
686
|
+
# Set Faraday's HTTP adapter with support for HTTP/2
|
687
|
+
builder.adapter :typhoeus, http_version: :httpv2_0
|
561
688
|
end
|
562
689
|
end
|
563
690
|
|