ably-rest 1.0.5 → 1.1.3
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 +5 -5
- data/.travis.yml +6 -3
- data/CHANGELOG.md +1 -1
- data/LICENSE +1 -1
- data/README.md +26 -7
- data/SPEC.md +2003 -1605
- data/ably-rest.gemspec +4 -2
- data/lib/submodules/ably-ruby/.editorconfig +14 -0
- data/lib/submodules/ably-ruby/.travis.yml +10 -8
- data/lib/submodules/ably-ruby/CHANGELOG.md +97 -1
- 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 +17 -11
- data/lib/submodules/ably-ruby/lib/ably/auth.rb +34 -8
- data/lib/submodules/ably-ruby/lib/ably/exceptions.rb +10 -4
- data/lib/submodules/ably-ruby/lib/ably/logger.rb +8 -2
- 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 +12 -12
- data/lib/submodules/ably-ruby/lib/ably/models/message.rb +6 -4
- data/lib/submodules/ably-ruby/lib/ably/models/presence_message.rb +6 -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/async_wrapper.rb +2 -2
- data/lib/submodules/ably-ruby/lib/ably/modules/conversions.rb +2 -2
- data/lib/submodules/ably-ruby/lib/ably/modules/encodeable.rb +2 -2
- data/lib/submodules/ably-ruby/lib/ably/modules/event_emitter.rb +2 -2
- 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/safe_deferrable.rb +1 -1
- data/lib/submodules/ably-ruby/lib/ably/modules/safe_yield.rb +1 -1
- data/lib/submodules/ably-ruby/lib/ably/modules/state_emitter.rb +5 -5
- 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 +2 -2
- data/lib/submodules/ably-ruby/lib/ably/realtime/channel.rb +27 -105
- data/lib/submodules/ably-ruby/lib/ably/realtime/channel/channel_manager.rb +4 -8
- 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 +9 -4
- 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 +45 -26
- 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 +2 -2
- data/lib/submodules/ably-ruby/lib/ably/realtime/presence.rb +7 -7
- data/lib/submodules/ably-ruby/lib/ably/realtime/presence/members_map.rb +9 -9
- 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 +54 -18
- data/lib/submodules/ably-ruby/lib/ably/rest/channel/push_channel.rb +62 -0
- data/lib/submodules/ably-ruby/lib/ably/rest/client.rb +171 -41
- 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 +253 -49
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_history_spec.rb +33 -21
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_spec.rb +180 -62
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/client_spec.rb +155 -2
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_failures_spec.rb +293 -13
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_spec.rb +142 -39
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/message_spec.rb +38 -36
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/presence_history_spec.rb +12 -3
- data/lib/submodules/ably-ruby/spec/acceptance/realtime/presence_spec.rb +207 -173
- 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 +62 -51
- 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 +318 -74
- 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/shared/client_initializer_behaviour.rb +1 -9
- data/lib/submodules/ably-ruby/spec/spec_helper.rb +3 -1
- data/lib/submodules/ably-ruby/spec/support/debug_failure_helper.rb +9 -5
- data/lib/submodules/ably-ruby/spec/support/event_emitter_helper.rb +31 -0
- data/lib/submodules/ably-ruby/spec/support/event_machine_helper.rb +1 -1
- data/lib/submodules/ably-ruby/spec/support/test_app.rb +2 -2
- data/lib/submodules/ably-ruby/spec/support/test_logger_helper.rb +42 -0
- data/lib/submodules/ably-ruby/spec/unit/logger_spec.rb +11 -12
- 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/async_wrapper_spec.rb +2 -2
- data/lib/submodules/ably-ruby/spec/unit/modules/enum_spec.rb +1 -1
- data/lib/submodules/ably-ruby/spec/unit/modules/event_emitter_spec.rb +3 -3
- data/lib/submodules/ably-ruby/spec/unit/modules/state_emitter_spec.rb +10 -10
- data/lib/submodules/ably-ruby/spec/unit/realtime/channel_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 +2 -2
- data/lib/submodules/ably-ruby/spec/unit/realtime/presence_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 +30 -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
- data/lib/submodules/ably-ruby/spec/unit/util/pub_sub_spec.rb +3 -3
- data/spec/spec_helper.rb +1 -0
- metadata +51 -10
@@ -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
|
#
|
@@ -22,22 +32,22 @@ module Ably
|
|
22
32
|
# @option channel_options [Hash,Ably::Models::CipherParams] :cipher A hash of options or a {Ably::Models::CipherParams} to configure the encryption. *:key* is required, all other options are optional. See {Ably::Util::Crypto#initialize} for a list of +:cipher+ options
|
23
33
|
#
|
24
34
|
def initialize(client, name, channel_options = {})
|
25
|
-
ensure_utf_8 :name, name
|
35
|
+
name = (ensure_utf_8 :name, name)
|
26
36
|
|
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
|
@@ -30,6 +30,15 @@ module Ably
|
|
30
30
|
max_retry_count: 3
|
31
31
|
}.freeze
|
32
32
|
|
33
|
+
FALLBACK_RETRY_TIMEOUT = 10 * 60
|
34
|
+
|
35
|
+
# Faraday 1.0 introduced new error types, however we want to support Faraday <1 too which only used Faraday::ClientError
|
36
|
+
FARADAY_CLIENT_OR_SERVER_ERRORS = if defined?(Faraday::ParsingError)
|
37
|
+
[Faraday::ClientError, Faraday::ServerError, Faraday::ConnectionFailed, Faraday::SSLError, Faraday::ParsingError]
|
38
|
+
else
|
39
|
+
Faraday::ClientError
|
40
|
+
end
|
41
|
+
|
33
42
|
def_delegators :auth, :client_id, :auth_options
|
34
43
|
|
35
44
|
# Custom environment to use such as 'sandbox' when testing the client library against an alternate Ably environment
|
@@ -83,10 +92,23 @@ module Ably
|
|
83
92
|
# if empty or nil then fallback host functionality is disabled
|
84
93
|
attr_reader :fallback_hosts
|
85
94
|
|
86
|
-
#
|
95
|
+
# Whether the {Client} has to add a random identifier to the path of a request
|
87
96
|
# @return [Boolean]
|
88
97
|
attr_reader :add_request_ids
|
89
98
|
|
99
|
+
# Retries are logged by default to warn and error. When true, retries are logged at info level
|
100
|
+
# @return [Boolean]
|
101
|
+
# @api private
|
102
|
+
attr_reader :log_retries_as_info
|
103
|
+
|
104
|
+
# True when idempotent publishing is enabled for all messages published via REST.
|
105
|
+
# When this feature is enabled, the client library will add a unique ID to every published message (without an ID)
|
106
|
+
# ensuring any failed published attempts (due to failures such as HTTP requests failing mid-flight) that are
|
107
|
+
# automatically retried will not result in duplicate messages being published to the Ably platform.
|
108
|
+
# Note: This is a beta unsupported feature!
|
109
|
+
# @return [Boolean]
|
110
|
+
attr_reader :idempotent_rest_publishing
|
111
|
+
|
90
112
|
# Creates a {Ably::Rest::Client Rest Client} and configures the {Ably::Auth} object for the connection.
|
91
113
|
#
|
92
114
|
# @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 +139,10 @@ module Ably
|
|
117
139
|
#
|
118
140
|
# @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
141
|
# @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
|
142
|
+
# @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
|
143
|
+
#
|
144
|
+
# @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
|
+
# @option options [Boolean] :idempotent_rest_publishing (false if ver < 1.2) When true, idempotent publishing is enabled for all messages published via REST
|
120
146
|
#
|
121
147
|
# @return [Ably::Rest::Client]
|
122
148
|
#
|
@@ -139,18 +165,21 @@ module Ably
|
|
139
165
|
end
|
140
166
|
end
|
141
167
|
|
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
|
168
|
+
@realtime_client = options.delete(:realtime_client)
|
169
|
+
@tls = options.delete(:tls) == false ? false : true
|
170
|
+
@environment = options.delete(:environment) # nil is production
|
171
|
+
@environment = nil if [:production, 'production'].include?(@environment)
|
172
|
+
@protocol = options.delete(:protocol) || :msgpack
|
173
|
+
@debug_http = options.delete(:debug_http)
|
174
|
+
@log_level = options.delete(:log_level) || ::Logger::WARN
|
175
|
+
@custom_logger = options.delete(:logger)
|
176
|
+
@custom_host = options.delete(:rest_host)
|
177
|
+
@custom_port = options.delete(:port)
|
178
|
+
@custom_tls_port = options.delete(:tls_port)
|
179
|
+
@add_request_ids = options.delete(:add_request_ids)
|
180
|
+
@log_retries_as_info = options.delete(:log_retries_as_info)
|
181
|
+
@idempotent_rest_publishing = options.delete(:idempotent_rest_publishing) || Ably.major_minor_version_numeric > 1.1
|
182
|
+
|
154
183
|
|
155
184
|
if options[:fallback_hosts_use_default] && options[:fallback_jhosts]
|
156
185
|
raise ArgumentError, "fallback_hosts_use_default cannot be set to trye when fallback_jhosts is also provided"
|
@@ -166,6 +195,10 @@ module Ably
|
|
166
195
|
Ably::FALLBACK_HOSTS
|
167
196
|
end
|
168
197
|
|
198
|
+
options[:fallback_retry_timeout] ||= FALLBACK_RETRY_TIMEOUT
|
199
|
+
|
200
|
+
# Take option keys prefixed with `http_`, remove the http_ and
|
201
|
+
# check if the option exists in HTTP_DEFAULTS. If so, update http_defaults
|
169
202
|
@http_defaults = HTTP_DEFAULTS.dup
|
170
203
|
options.each do |key, val|
|
171
204
|
if http_key = key[/^http_(.+)/, 1]
|
@@ -190,8 +223,12 @@ module Ably
|
|
190
223
|
raise ArgumentError, 'Protocol is invalid. Must be either :msgpack or :json' unless [:msgpack, :json].include?(@protocol)
|
191
224
|
|
192
225
|
token_params = options.delete(:default_token_params) || {}
|
193
|
-
@options
|
194
|
-
|
226
|
+
@options = options
|
227
|
+
init_auth_options = options.select do |key, _|
|
228
|
+
Auth::AUTH_OPTIONS_KEYS.include?(key.to_s)
|
229
|
+
end
|
230
|
+
|
231
|
+
@auth = Auth.new(self, token_params, init_auth_options)
|
195
232
|
@channels = Ably::Rest::Channels.new(self)
|
196
233
|
@encoders = []
|
197
234
|
|
@@ -273,6 +310,24 @@ module Ably
|
|
273
310
|
raw_request(:post, path, params, options)
|
274
311
|
end
|
275
312
|
|
313
|
+
# Perform an HTTP PUT request to the API using configured authentication
|
314
|
+
#
|
315
|
+
# @return [Faraday::Response]
|
316
|
+
#
|
317
|
+
# @api private
|
318
|
+
def put(path, params, options = {})
|
319
|
+
raw_request(:put, path, params, options)
|
320
|
+
end
|
321
|
+
|
322
|
+
# Perform an HTTP DELETE request to the API using configured authentication
|
323
|
+
#
|
324
|
+
# @return [Faraday::Response]
|
325
|
+
#
|
326
|
+
# @api private
|
327
|
+
def delete(path, params, options = {})
|
328
|
+
raw_request(:delete, path, params, options)
|
329
|
+
end
|
330
|
+
|
276
331
|
# Perform an HTTP request to the Ably API
|
277
332
|
# This is a convenience for customers who wish to use bleeding edge REST API functionality
|
278
333
|
# that is either not documented or is not included in the API for our client libraries.
|
@@ -292,14 +347,14 @@ module Ably
|
|
292
347
|
|
293
348
|
response = case method.to_sym
|
294
349
|
when :get
|
295
|
-
|
350
|
+
reauthorize_on_authorization_failure do
|
296
351
|
send_request(method, path, params, headers: headers)
|
297
352
|
end
|
298
353
|
when :post
|
299
354
|
path_with_params = Addressable::URI.new
|
300
355
|
path_with_params.query_values = params || {}
|
301
356
|
query = path_with_params.query
|
302
|
-
|
357
|
+
reauthorize_on_authorization_failure do
|
303
358
|
send_request(method, "#{path}#{"?#{query}" unless query.nil? || query.empty?}", body, headers: headers)
|
304
359
|
end
|
305
360
|
end
|
@@ -321,6 +376,20 @@ module Ably
|
|
321
376
|
Models::HttpPaginatedResponse.new(response, path, self)
|
322
377
|
end
|
323
378
|
|
379
|
+
# The local device detilas
|
380
|
+
# @return [Ably::Models::LocalDevice]
|
381
|
+
#
|
382
|
+
# @note This is unsupported in the Ruby library
|
383
|
+
def device
|
384
|
+
raise Ably::Exceptions::PushNotificationsNotSupported, 'This device does not support receiving or subscribing to push notifications. The local device object is not unavailable'
|
385
|
+
end
|
386
|
+
|
387
|
+
# Push notification object for publishing and managing push notifications
|
388
|
+
# @return [Ably::Rest::Push]
|
389
|
+
def push
|
390
|
+
@push ||= Push.new(self)
|
391
|
+
end
|
392
|
+
|
324
393
|
# @!attribute [r] endpoint
|
325
394
|
# @return [URI::Generic] Default Ably REST endpoint used for all requests
|
326
395
|
def endpoint
|
@@ -389,7 +458,6 @@ module Ably
|
|
389
458
|
def fallback_connection
|
390
459
|
unless defined?(@fallback_connections) && @fallback_connections
|
391
460
|
@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
461
|
end
|
394
462
|
@fallback_index ||= 0
|
395
463
|
|
@@ -410,23 +478,58 @@ module Ably
|
|
410
478
|
|
411
479
|
# Allowable duration for an external auth request
|
412
480
|
# For REST client this defaults to request_timeout
|
413
|
-
# For Realtime clients this defaults to realtime_request_timeout
|
481
|
+
# For Realtime clients this defaults to 250ms less than the realtime_request_timeout
|
482
|
+
# ensuring an auth failure will be triggered before the realtime request timeout fires
|
483
|
+
# which would lead to a misleading error message (connection timeout as opposed to auth request timeout)
|
414
484
|
# @api private
|
415
485
|
def auth_request_timeout
|
416
486
|
if @realtime_client
|
417
|
-
@realtime_client.connection.defaults.fetch(:realtime_request_timeout)
|
487
|
+
@realtime_client.connection.defaults.fetch(:realtime_request_timeout) - 0.25
|
418
488
|
else
|
419
489
|
http_defaults.fetch(:request_timeout)
|
420
490
|
end
|
421
491
|
end
|
422
492
|
|
493
|
+
# If the primary host endpoint fails, and a subsequent fallback host succeeds, the fallback
|
494
|
+
# host that succeeded is used for +ClientOption+ +fallback_retry_timeout+ seconds to avoid
|
495
|
+
# retries to known failing hosts for a short period of time.
|
496
|
+
# See https://github.com/ably/docs/pull/554, spec id #RSC15f
|
497
|
+
#
|
498
|
+
# @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
|
499
|
+
def using_preferred_fallback_host?
|
500
|
+
if preferred_fallback_connection && (preferred_fallback_connection.fetch(:expires_at) > Time.now)
|
501
|
+
preferred_fallback_connection.fetch(:connection_object).host
|
502
|
+
end
|
503
|
+
end
|
504
|
+
|
423
505
|
private
|
506
|
+
|
507
|
+
attr_reader :preferred_fallback_connection
|
508
|
+
|
509
|
+
# See #using_preferred_fallback_host? for context
|
510
|
+
def set_preferred_fallback_connection(connection)
|
511
|
+
@preferred_fallback_connection = if connection == @connection
|
512
|
+
# If the succeeded connection is in fact the primary connection (tried after a failed fallback)
|
513
|
+
# then clear the preferred fallback connection
|
514
|
+
nil
|
515
|
+
else
|
516
|
+
{
|
517
|
+
expires_at: Time.now + options.fetch(:fallback_retry_timeout),
|
518
|
+
connection_object: connection,
|
519
|
+
}
|
520
|
+
end
|
521
|
+
end
|
522
|
+
|
523
|
+
def get_preferred_fallback_connection_object
|
524
|
+
preferred_fallback_connection.fetch(:connection_object) if using_preferred_fallback_host?
|
525
|
+
end
|
526
|
+
|
424
527
|
def raw_request(method, path, params = {}, options = {})
|
425
528
|
options = options.clone
|
426
529
|
if options.delete(:disable_automatic_reauthorize) == true
|
427
530
|
send_request(method, path, params, options)
|
428
531
|
else
|
429
|
-
|
532
|
+
reauthorize_on_authorization_failure do
|
430
533
|
send_request(method, path, params, options)
|
431
534
|
end
|
432
535
|
end
|
@@ -439,25 +542,33 @@ module Ably
|
|
439
542
|
max_retry_duration = http_defaults.fetch(:max_retry_duration)
|
440
543
|
requested_at = Time.now
|
441
544
|
retry_count = 0
|
442
|
-
|
443
|
-
if add_request_ids
|
444
|
-
|
445
|
-
|
446
|
-
else
|
447
|
-
params.dup
|
448
|
-
end
|
449
|
-
request_id = SecureRandom.urlsafe_base64(10)
|
450
|
-
params[:request_id] = request_id
|
451
|
-
end
|
545
|
+
retry_sequence_id = nil
|
546
|
+
request_id = SecureRandom.urlsafe_base64(10) if add_request_ids
|
547
|
+
|
548
|
+
preferred_fallback_connection_for_first_request = get_preferred_fallback_connection_object
|
452
549
|
|
453
550
|
begin
|
454
|
-
use_fallback = can_fallback_to_alternate_ably_host? && retry_count > 0
|
551
|
+
use_fallback = can_fallback_to_alternate_ably_host? && (retry_count > 0)
|
552
|
+
|
553
|
+
conn = if preferred_fallback_connection_for_first_request
|
554
|
+
case retry_count
|
555
|
+
when 0
|
556
|
+
preferred_fallback_connection_for_first_request
|
557
|
+
when 1
|
558
|
+
# Ensure the root host is used first if the preferred fallback fails, see #RSC15f
|
559
|
+
connection(use_fallback: false)
|
560
|
+
end
|
561
|
+
end || connection(use_fallback: use_fallback) # default to normal connection selection process if not preferred connection set
|
455
562
|
|
456
|
-
|
563
|
+
conn.send(method, path, params) do |request|
|
457
564
|
if add_request_ids
|
565
|
+
request.params[:request_id] = request_id
|
458
566
|
request.options.context = {} if request.options.context.nil?
|
459
567
|
request.options.context[:request_id] = request_id
|
460
568
|
end
|
569
|
+
if options[:qs_params]
|
570
|
+
request.params.merge!(options[:qs_params])
|
571
|
+
end
|
461
572
|
unless options[:send_auth_header] == false
|
462
573
|
request.headers[:authorization] = auth.auth_header
|
463
574
|
if options[:headers]
|
@@ -466,28 +577,47 @@ module Ably
|
|
466
577
|
end
|
467
578
|
end
|
468
579
|
end
|
580
|
+
end.tap do
|
581
|
+
if retry_count > 0
|
582
|
+
retry_log_severity = log_retries_as_info ? :info : :warn
|
583
|
+
logger.public_send(retry_log_severity) do
|
584
|
+
"Ably::Rest::Client - Request SUCCEEDED after #{retry_count} #{retry_count > 1 ? 'retries' : 'retry' } for" \
|
585
|
+
" #{method} #{path} #{params} (seq ##{retry_sequence_id}, time elapsed #{(Time.now.to_f - requested_at.to_f).round(2)}s)"
|
586
|
+
end
|
587
|
+
set_preferred_fallback_connection conn
|
588
|
+
end
|
469
589
|
end
|
470
590
|
|
471
|
-
rescue Faraday::TimeoutError,
|
591
|
+
rescue *([Faraday::TimeoutError, Ably::Exceptions::ServerError] + FARADAY_CLIENT_OR_SERVER_ERRORS) => error
|
592
|
+
retry_sequence_id ||= SecureRandom.urlsafe_base64(4)
|
472
593
|
time_passed = Time.now - requested_at
|
473
|
-
|
594
|
+
|
595
|
+
if can_fallback_to_alternate_ably_host? && (retry_count < max_retry_count) && (time_passed <= max_retry_duration)
|
474
596
|
retry_count += 1
|
475
|
-
|
597
|
+
retry_log_severity = log_retries_as_info ? :info : :warn
|
598
|
+
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}" }
|
476
599
|
retry
|
477
600
|
end
|
601
|
+
|
602
|
+
retry_log_severity = log_retries_as_info ? :info : :error
|
603
|
+
logger.public_send(retry_log_severity) do
|
604
|
+
"Ably::Rest::Client - Request FAILED after #{retry_count} #{retry_count > 1 ? 'retries' : 'retry' } for" \
|
605
|
+
" #{method} #{path} #{params} (seq ##{retry_sequence_id}, time elapsed #{(Time.now.to_f - requested_at.to_f).round(2)}s)"
|
606
|
+
end
|
607
|
+
|
478
608
|
case error
|
479
609
|
when Faraday::TimeoutError
|
480
|
-
raise Ably::Exceptions::ConnectionTimeout.new(error.message, nil,
|
481
|
-
when
|
610
|
+
raise Ably::Exceptions::ConnectionTimeout.new(error.message, nil, Ably::Exceptions::Codes::CONNECTION_TIMED_OUT, error, { request_id: request_id })
|
611
|
+
when *FARADAY_CLIENT_OR_SERVER_ERRORS
|
482
612
|
# request_id is also available in the request context
|
483
|
-
raise Ably::Exceptions::ConnectionError.new(error.message, nil,
|
613
|
+
raise Ably::Exceptions::ConnectionError.new(error.message, nil, Ably::Exceptions::Codes::CONNECTION_FAILED, error, { request_id: request_id })
|
484
614
|
else
|
485
615
|
raise error
|
486
616
|
end
|
487
617
|
end
|
488
618
|
end
|
489
619
|
|
490
|
-
def
|
620
|
+
def reauthorize_on_authorization_failure
|
491
621
|
yield
|
492
622
|
rescue Ably::Exceptions::TokenExpired => e
|
493
623
|
if auth.token_renewable?
|
@@ -549,7 +679,7 @@ module Ably
|
|
549
679
|
setup_incoming_middleware builder, logger, fail_if_unsupported_mime_type: true
|
550
680
|
|
551
681
|
# Set Faraday's HTTP adapter
|
552
|
-
builder.adapter
|
682
|
+
builder.adapter :excon
|
553
683
|
end
|
554
684
|
end
|
555
685
|
|