ably-pubsub-core 0.0.1.pre
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 +7 -0
- data/lib/ably/agent.rb +8 -0
- data/lib/ably/auth.rb +791 -0
- data/lib/ably/exceptions.rb +169 -0
- data/lib/ably/logger.rb +119 -0
- data/lib/ably/models/auth_details.rb +45 -0
- data/lib/ably/models/channel_details.rb +69 -0
- data/lib/ably/models/channel_metrics.rb +107 -0
- data/lib/ably/models/channel_occupancy.rb +50 -0
- data/lib/ably/models/channel_options.rb +120 -0
- data/lib/ably/models/channel_state_change.rb +84 -0
- data/lib/ably/models/channel_status.rb +63 -0
- data/lib/ably/models/cipher_params.rb +137 -0
- data/lib/ably/models/connection_details.rb +149 -0
- data/lib/ably/models/connection_state_change.rb +83 -0
- data/lib/ably/models/delta_extras.rb +28 -0
- data/lib/ably/models/device_details.rb +126 -0
- data/lib/ably/models/device_push_details.rb +94 -0
- data/lib/ably/models/error_info.rb +104 -0
- data/lib/ably/models/http_paginated_response.rb +112 -0
- data/lib/ably/models/idiomatic_ruby_wrapper.rb +235 -0
- data/lib/ably/models/message.rb +223 -0
- data/lib/ably/models/message_encoders/base.rb +135 -0
- data/lib/ably/models/message_encoders/base64.rb +40 -0
- data/lib/ably/models/message_encoders/cipher.rb +84 -0
- data/lib/ably/models/message_encoders/json.rb +34 -0
- data/lib/ably/models/message_encoders/utf8.rb +26 -0
- data/lib/ably/models/nil_logger.rb +20 -0
- data/lib/ably/models/paginated_result.rb +201 -0
- data/lib/ably/models/presence_message.rb +244 -0
- data/lib/ably/models/protocol_message.rb +288 -0
- data/lib/ably/models/push_channel_subscription.rb +114 -0
- data/lib/ably/models/stats.rb +242 -0
- data/lib/ably/models/stats_types.rb +107 -0
- data/lib/ably/models/token_details.rb +148 -0
- data/lib/ably/models/token_request.rb +180 -0
- data/lib/ably/modules/ably.rb +26 -0
- data/lib/ably/modules/async_wrapper.rb +69 -0
- data/lib/ably/modules/channels_collection.rb +105 -0
- data/lib/ably/modules/conversions.rb +153 -0
- data/lib/ably/modules/encodeable.rb +111 -0
- data/lib/ably/modules/enum.rb +230 -0
- data/lib/ably/modules/event_emitter.rb +200 -0
- data/lib/ably/modules/event_machine_helpers.rb +26 -0
- data/lib/ably/modules/exception_codes.rb +128 -0
- data/lib/ably/modules/http_helpers.rb +41 -0
- data/lib/ably/modules/message_emitter.rb +78 -0
- data/lib/ably/modules/message_pack.rb +14 -0
- data/lib/ably/modules/model_common.rb +84 -0
- data/lib/ably/modules/safe_deferrable.rb +83 -0
- data/lib/ably/modules/safe_yield.rb +41 -0
- data/lib/ably/modules/state_emitter.rb +181 -0
- data/lib/ably/modules/state_machine.rb +59 -0
- data/lib/ably/modules/statesman_monkey_patch.rb +33 -0
- data/lib/ably/modules/uses_state_machine.rb +111 -0
- data/lib/ably/realtime/auth.rb +283 -0
- data/lib/ably/realtime/channel/channel_manager.rb +272 -0
- data/lib/ably/realtime/channel/channel_properties.rb +40 -0
- data/lib/ably/realtime/channel/channel_state_machine.rb +104 -0
- data/lib/ably/realtime/channel/publisher.rb +82 -0
- data/lib/ably/realtime/channel/push_channel.rb +69 -0
- data/lib/ably/realtime/channel.rb +440 -0
- data/lib/ably/realtime/channels.rb +71 -0
- data/lib/ably/realtime/client/incoming_message_dispatcher.rb +225 -0
- data/lib/ably/realtime/client/outgoing_message_dispatcher.rb +87 -0
- data/lib/ably/realtime/client.rb +363 -0
- data/lib/ably/realtime/connection/connection_manager.rb +594 -0
- data/lib/ably/realtime/connection/connection_state_machine.rb +144 -0
- data/lib/ably/realtime/connection/websocket_transport.rb +287 -0
- data/lib/ably/realtime/connection.rb +738 -0
- data/lib/ably/realtime/models/nil_channel.rb +30 -0
- data/lib/ably/realtime/presence/members_map.rb +395 -0
- data/lib/ably/realtime/presence/presence_manager.rb +78 -0
- data/lib/ably/realtime/presence/presence_state_machine.rb +57 -0
- data/lib/ably/realtime/presence.rb +502 -0
- data/lib/ably/realtime/push/admin.rb +75 -0
- data/lib/ably/realtime/push/channel_subscriptions.rb +108 -0
- data/lib/ably/realtime/push/device_registrations.rb +105 -0
- data/lib/ably/realtime/push.rb +25 -0
- data/lib/ably/realtime/recovery_key_context.rb +36 -0
- data/lib/ably/realtime.rb +60 -0
- data/lib/ably/rest/channel/push_channel.rb +59 -0
- data/lib/ably/rest/channel.rb +194 -0
- data/lib/ably/rest/channels.rb +47 -0
- data/lib/ably/rest/client.rb +727 -0
- data/lib/ably/rest/middleware/encoder.rb +49 -0
- data/lib/ably/rest/middleware/exceptions.rb +53 -0
- data/lib/ably/rest/middleware/external_exceptions.rb +24 -0
- data/lib/ably/rest/middleware/fail_if_unsupported_mime_type.rb +22 -0
- data/lib/ably/rest/middleware/logger.rb +58 -0
- data/lib/ably/rest/middleware/parse_json.rb +27 -0
- data/lib/ably/rest/middleware/parse_message_pack.rb +43 -0
- data/lib/ably/rest/presence.rb +113 -0
- data/lib/ably/rest/push/admin.rb +58 -0
- data/lib/ably/rest/push/channel_subscriptions.rb +121 -0
- data/lib/ably/rest/push/device_registrations.rb +114 -0
- data/lib/ably/rest/push.rb +25 -0
- data/lib/ably/rest.rb +40 -0
- data/lib/ably/util/ably_extensions.rb +29 -0
- data/lib/ably/util/crypto.rb +134 -0
- data/lib/ably/util/pub_sub.rb +45 -0
- data/lib/ably/util/safe_deferrable.rb +44 -0
- data/lib/ably/version.rb +7 -0
- data/lib/ably.rb +16 -0
- metadata +497 -0
|
@@ -0,0 +1,727 @@
|
|
|
1
|
+
require 'faraday'
|
|
2
|
+
require 'json'
|
|
3
|
+
require 'logger'
|
|
4
|
+
require 'uri'
|
|
5
|
+
|
|
6
|
+
require 'typhoeus'
|
|
7
|
+
require 'faraday/typhoeus'
|
|
8
|
+
|
|
9
|
+
require 'ably/rest/middleware/exceptions'
|
|
10
|
+
|
|
11
|
+
module Ably
|
|
12
|
+
module Rest
|
|
13
|
+
# A client that offers a simple stateless API to interact directly with Ably's REST API.
|
|
14
|
+
#
|
|
15
|
+
class Client
|
|
16
|
+
include Ably::Modules::Conversions
|
|
17
|
+
include Ably::Modules::HttpHelpers
|
|
18
|
+
extend Forwardable
|
|
19
|
+
using Ably::Util::AblyExtensions
|
|
20
|
+
|
|
21
|
+
# Default Ably domain for REST
|
|
22
|
+
DOMAIN = 'rest.ably.io'
|
|
23
|
+
|
|
24
|
+
MAX_MESSAGE_SIZE = 65536 # See spec TO3l8
|
|
25
|
+
MAX_FRAME_SIZE = 524288 # See spec TO3l8
|
|
26
|
+
|
|
27
|
+
# Configuration for HTTP timeouts and HTTP request reattempts to fallback hosts
|
|
28
|
+
HTTP_DEFAULTS = {
|
|
29
|
+
open_timeout: 4,
|
|
30
|
+
request_timeout: 10,
|
|
31
|
+
max_retry_duration: 15,
|
|
32
|
+
max_retry_count: 3
|
|
33
|
+
}.freeze
|
|
34
|
+
|
|
35
|
+
FALLBACK_RETRY_TIMEOUT = 10 * 60
|
|
36
|
+
|
|
37
|
+
# Faraday 1.0 introduced new error types, however we want to support Faraday <1 too which only used Faraday::ClientError
|
|
38
|
+
FARADAY_CLIENT_OR_SERVER_ERRORS = if defined?(Faraday::ParsingError)
|
|
39
|
+
[Faraday::ClientError, Faraday::ServerError, Faraday::ConnectionFailed, Faraday::SSLError, Faraday::ParsingError]
|
|
40
|
+
else
|
|
41
|
+
Faraday::ClientError
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def_delegators :auth, :client_id, :auth_options
|
|
45
|
+
|
|
46
|
+
# Custom environment to use such as 'sandbox' when testing the client library against an alternate Ably environment
|
|
47
|
+
# @return [String]
|
|
48
|
+
attr_reader :environment
|
|
49
|
+
|
|
50
|
+
# The protocol configured for this client, either binary `:msgpack` or text based `:json`
|
|
51
|
+
# @return [Symbol]
|
|
52
|
+
attr_reader :protocol
|
|
53
|
+
|
|
54
|
+
# Client agent i.e. `example-gem/1.2.0 ably-pubsub-ruby/1.1.5 ruby/3.1.1`
|
|
55
|
+
# @return [String]
|
|
56
|
+
attr_reader :agent
|
|
57
|
+
|
|
58
|
+
# An {Ably::Auth} object.
|
|
59
|
+
# @spec RSC5
|
|
60
|
+
# @return [Ably::Auth]
|
|
61
|
+
attr_reader :auth
|
|
62
|
+
|
|
63
|
+
# A {Ably::Rest::Channels} object.
|
|
64
|
+
# @spec RSN1
|
|
65
|
+
# @return [Aby::Rest::Channels]
|
|
66
|
+
attr_reader :channels
|
|
67
|
+
|
|
68
|
+
# Log level configured for this {Client}
|
|
69
|
+
# @return [Logger::Severity]
|
|
70
|
+
attr_reader :log_level
|
|
71
|
+
|
|
72
|
+
# The custom host that is being used if it was provided with the option +:rest_host+ when the {Client} was created
|
|
73
|
+
# @return [String,Nil]
|
|
74
|
+
attr_reader :custom_host
|
|
75
|
+
|
|
76
|
+
# The custom port for non-TLS requests if it was provided with the option +:port+ when the {Client} was created
|
|
77
|
+
# @return [Integer,Nil]
|
|
78
|
+
attr_reader :custom_port
|
|
79
|
+
|
|
80
|
+
# The custom TLS port for TLS requests if it was provided with the option +:tls_port+ when the {Client} was created
|
|
81
|
+
# @return [Integer,Nil]
|
|
82
|
+
attr_reader :custom_tls_port
|
|
83
|
+
|
|
84
|
+
# The immutable configured HTTP defaults for this client.
|
|
85
|
+
# See {#initialize} for the configurable HTTP defaults prefixed with +http_+
|
|
86
|
+
# @return [Hash]
|
|
87
|
+
attr_reader :http_defaults
|
|
88
|
+
|
|
89
|
+
# The registered encoders that are used to encode and decode message payloads
|
|
90
|
+
# @return [Array<Ably::Models::MessageEncoder::Base>]
|
|
91
|
+
# @api private
|
|
92
|
+
attr_reader :encoders
|
|
93
|
+
|
|
94
|
+
# The additional options passed to this Client's #initialize method not available as attributes of this class
|
|
95
|
+
# @return [Hash]
|
|
96
|
+
# @api private
|
|
97
|
+
attr_reader :options
|
|
98
|
+
|
|
99
|
+
# The list of fallback hosts to be used by this client
|
|
100
|
+
# if empty or nil then fallback host functionality is disabled
|
|
101
|
+
attr_reader :fallback_hosts
|
|
102
|
+
|
|
103
|
+
# Whether the {Client} has to add a random identifier to the path of a request
|
|
104
|
+
# @return [Boolean]
|
|
105
|
+
attr_reader :add_request_ids
|
|
106
|
+
|
|
107
|
+
# Retries are logged by default to warn and error. When true, retries are logged at info level
|
|
108
|
+
# @return [Boolean]
|
|
109
|
+
# @api private
|
|
110
|
+
attr_reader :log_retries_as_info
|
|
111
|
+
|
|
112
|
+
# True when idempotent publishing is enabled for all messages published via REST.
|
|
113
|
+
# When this feature is enabled, the client library will add a unique ID to every published message (without an ID)
|
|
114
|
+
# ensuring any failed published attempts (due to failures such as HTTP requests failing mid-flight) that are
|
|
115
|
+
# automatically retried will not result in duplicate messages being published to the Ably platform.
|
|
116
|
+
# Note: This is a beta unsupported feature!
|
|
117
|
+
# @return [Boolean]
|
|
118
|
+
attr_reader :idempotent_rest_publishing
|
|
119
|
+
|
|
120
|
+
# Max message size (TO2, TO3l8) by default (65536 bytes) 64KiB
|
|
121
|
+
# @return [Integer]
|
|
122
|
+
attr_reader :max_message_size
|
|
123
|
+
|
|
124
|
+
# Max frame size (TO2, TO3l8) by default (524288 bytes) 512KiB
|
|
125
|
+
# @return [Integer]
|
|
126
|
+
attr_reader :max_frame_size
|
|
127
|
+
|
|
128
|
+
# Constructs a {Ably::Rest::Client} object using an Ably API key or token string.
|
|
129
|
+
#
|
|
130
|
+
# @spec RSC1
|
|
131
|
+
#
|
|
132
|
+
# @param [Hash,String] options an options Hash or String used to configure the client and the authentication, or String with an API key or Token ID
|
|
133
|
+
# @option options [Boolean] :tls (true) When false, TLS is disabled. Please note Basic Auth is disallowed without TLS as secrets cannot be transmitted over unsecured connections.
|
|
134
|
+
# @option options [String] :key API key comprising the key name and key secret in a single string
|
|
135
|
+
# @option options [String] :token Token string or {Models::TokenDetails} used to authenticate requests
|
|
136
|
+
# @option options [String] :token_details {Models::TokenDetails} used to authenticate requests
|
|
137
|
+
# @option options [Boolean] :use_token_auth Will force Basic Auth if set to false, and Token auth if set to true
|
|
138
|
+
# @option options [String] :environment Specify 'sandbox' when testing the client library against an alternate Ably environment
|
|
139
|
+
# @option options [Symbol] :protocol (:msgpack) Protocol used to communicate with Ably, :json and :msgpack currently supported
|
|
140
|
+
# @option options [Boolean] :use_binary_protocol (true) When true will use the MessagePack binary protocol, when false it will use JSON encoding. This option will overide :protocol option
|
|
141
|
+
# @option options [Logger::Severity,Symbol] :log_level (Logger::WARN) Log level for the standard Logger that outputs to STDOUT. Can be set to :fatal (Logger::FATAL), :error (Logger::ERROR), :warn (Logger::WARN), :info (Logger::INFO), :debug (Logger::DEBUG) or :none
|
|
142
|
+
# @option options [Logger] :logger A custom logger can be used however it must adhere to the Ruby Logger interface, see http://www.ruby-doc.org/stdlib-3.1.1/libdoc/logger/rdoc/Logger.html
|
|
143
|
+
# @option options [String] :client_id client ID identifying this connection to other clients
|
|
144
|
+
# @option options [String] :auth_url a URL to be used to GET or POST a set of token request params, to obtain a signed token request
|
|
145
|
+
# @option options [Hash] :auth_headers a set of application-specific headers to be added to any request made to the +auth_url+
|
|
146
|
+
# @option options [Hash] :auth_params a set of application-specific query params to be added to any request made to the +auth_url+
|
|
147
|
+
# @option options [Symbol] :auth_method (:get) HTTP method to use with +auth_url+, must be either +:get+ or +:post+
|
|
148
|
+
# @option options [Proc] :auth_callback when provided, the Proc will be called with the token params hash as the first argument, whenever a new token is required.
|
|
149
|
+
# The Proc should return a token string, {Ably::Models::TokenDetails} or JSON equivalent, {Ably::Models::TokenRequest} or JSON equivalent
|
|
150
|
+
# @option options [Boolean] :query_time when true will query the {https://www.ably.com Ably} system for the current time instead of using the local time
|
|
151
|
+
# @option options [Hash] :default_token_params convenience to pass in +token_params+ that will be used as a default for all token requests. See {Auth#create_token_request}
|
|
152
|
+
#
|
|
153
|
+
# @option options [Integer] :http_open_timeout (4 seconds) timeout in seconds for opening an HTTP connection for all HTTP requests
|
|
154
|
+
# @option options [Integer] :http_request_timeout (10 seconds) timeout in seconds for any single complete HTTP request and response
|
|
155
|
+
# @option options [Integer] :http_max_retry_count (3) maximum number of fallback host retries for HTTP requests that fail due to network issues or server problems
|
|
156
|
+
# @option options [Integer] :http_max_retry_duration (15 seconds) maximum elapsed time in which fallback host retries for HTTP requests will be attempted i.e. if the first default host attempt takes 5s, and then the subsequent fallback retry attempt takes 7s, no further fallback host attempts will be made as the total elapsed time of 12s exceeds the default 10s limit
|
|
157
|
+
#
|
|
158
|
+
# @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
|
|
159
|
+
# @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
|
|
160
|
+
# @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
|
|
161
|
+
#
|
|
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.
|
|
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
|
|
166
|
+
#
|
|
167
|
+
# @return [Ably::Rest::Client]
|
|
168
|
+
#
|
|
169
|
+
# @example
|
|
170
|
+
# # Create a new client authenticating with basic auth using a String object
|
|
171
|
+
# client = Ably::Rest::Client.new('key.id:secret')
|
|
172
|
+
#
|
|
173
|
+
# # Construct a RestClient object using a Hash object.
|
|
174
|
+
# client = Ably::Rest::Client.new(key: 'key.id:secret', client_id: 'john')
|
|
175
|
+
#
|
|
176
|
+
def initialize(options)
|
|
177
|
+
raise ArgumentError, 'Options Hash is expected' if options.nil?
|
|
178
|
+
|
|
179
|
+
options = options.clone
|
|
180
|
+
if options.kind_of?(String)
|
|
181
|
+
options = if options.match(Auth::API_KEY_REGEX)
|
|
182
|
+
{ key: options }
|
|
183
|
+
else
|
|
184
|
+
{ token: options }
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
@agent = options.delete(:agent) || Ably::AGENT
|
|
189
|
+
# Additive agent entries (`identifier => version`), appended to the base agent
|
|
190
|
+
# string. This is how a package layered on this one (such as ably-pubsub-server)
|
|
191
|
+
# declares itself — including the side-declaring entry that MAU classification
|
|
192
|
+
# reads — without replacing the base identifiers the way :agent does.
|
|
193
|
+
options.delete(:agents).to_h.each do |identifier, version|
|
|
194
|
+
@agent = "#{@agent} #{version ? "#{identifier}/#{version}" : identifier}"
|
|
195
|
+
end
|
|
196
|
+
@realtime_client = options.delete(:realtime_client)
|
|
197
|
+
@tls = options.delete_with_default(:tls, true)
|
|
198
|
+
@environment = options.delete(:environment) # nil is production
|
|
199
|
+
@environment = nil if [:production, 'production'].include?(@environment)
|
|
200
|
+
@protocol = options.delete(:protocol) || :msgpack
|
|
201
|
+
@debug_http = options.delete(:debug_http)
|
|
202
|
+
@log_level = options.delete(:log_level) || ::Logger::WARN
|
|
203
|
+
@custom_logger = options.delete(:logger)
|
|
204
|
+
@custom_host = options.delete(:rest_host)
|
|
205
|
+
@custom_port = options.delete(:port)
|
|
206
|
+
@custom_tls_port = options.delete(:tls_port)
|
|
207
|
+
@add_request_ids = options.delete(:add_request_ids)
|
|
208
|
+
@log_retries_as_info = options.delete(:log_retries_as_info)
|
|
209
|
+
@max_message_size = options.delete(:max_message_size) || MAX_MESSAGE_SIZE
|
|
210
|
+
@max_frame_size = options.delete(:max_frame_size) || MAX_FRAME_SIZE
|
|
211
|
+
@idempotent_rest_publishing = options.delete_with_default(:idempotent_rest_publishing, true)
|
|
212
|
+
|
|
213
|
+
if options[:fallback_hosts_use_default] && options[:fallback_hosts]
|
|
214
|
+
raise ArgumentError, "fallback_hosts_use_default cannot be set to try when fallback_hosts is also provided"
|
|
215
|
+
end
|
|
216
|
+
@fallback_hosts = case
|
|
217
|
+
when options.delete(:fallback_hosts_use_default)
|
|
218
|
+
Ably::FALLBACK_HOSTS
|
|
219
|
+
when options_fallback_hosts = options.delete(:fallback_hosts)
|
|
220
|
+
options_fallback_hosts
|
|
221
|
+
when custom_host || options[:realtime_host] || custom_port || custom_tls_port
|
|
222
|
+
[]
|
|
223
|
+
when environment
|
|
224
|
+
CUSTOM_ENVIRONMENT_FALLBACKS_SUFFIXES.map { |host| "#{environment}#{host}" }
|
|
225
|
+
else
|
|
226
|
+
Ably::FALLBACK_HOSTS
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
options[:fallback_retry_timeout] ||= FALLBACK_RETRY_TIMEOUT
|
|
230
|
+
|
|
231
|
+
# Take option keys prefixed with `http_`, remove the http_ and
|
|
232
|
+
# check if the option exists in HTTP_DEFAULTS. If so, update http_defaults
|
|
233
|
+
@http_defaults = HTTP_DEFAULTS.dup
|
|
234
|
+
options.each do |key, val|
|
|
235
|
+
if http_key = key[/^http_(.+)/, 1]
|
|
236
|
+
# Typhoeus converts decimal durations to milliseconds, so 0.0001 timeout is treated as 0 (no timeout)
|
|
237
|
+
val = 0.001 if val.kind_of?(Numeric) && (val > 0) && (val < 0.001)
|
|
238
|
+
@http_defaults[http_key.to_sym] = val if val && @http_defaults.has_key?(http_key.to_sym)
|
|
239
|
+
end
|
|
240
|
+
end
|
|
241
|
+
@http_defaults.freeze
|
|
242
|
+
|
|
243
|
+
if @log_level == :none
|
|
244
|
+
@custom_logger = Ably::Models::NilLogger.new
|
|
245
|
+
else
|
|
246
|
+
@log_level = ::Logger.const_get(log_level.to_s.upcase) if log_level.kind_of?(Symbol) || log_level.kind_of?(String)
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
options.delete(:use_binary_protocol).tap do |use_binary_protocol|
|
|
250
|
+
if use_binary_protocol == true
|
|
251
|
+
@protocol = :msgpack
|
|
252
|
+
elsif use_binary_protocol == false
|
|
253
|
+
@protocol = :json
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
raise ArgumentError, 'Protocol is invalid. Must be either :msgpack or :json' unless [:msgpack, :json].include?(@protocol)
|
|
257
|
+
|
|
258
|
+
token_params = options.delete(:default_token_params) || {}
|
|
259
|
+
@options = options
|
|
260
|
+
init_auth_options = options.select do |key, _|
|
|
261
|
+
Auth::AUTH_OPTIONS_KEYS.include?(key.to_s)
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
@auth = Auth.new(self, token_params, init_auth_options)
|
|
265
|
+
@channels = Ably::Rest::Channels.new(self)
|
|
266
|
+
@encoders = []
|
|
267
|
+
|
|
268
|
+
options.freeze
|
|
269
|
+
|
|
270
|
+
initialize_default_encoders
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
# Return a REST {Ably::Rest::Channel} for the given name
|
|
274
|
+
#
|
|
275
|
+
# @param (see Ably::Rest::Channels#get)
|
|
276
|
+
#
|
|
277
|
+
# @return (see Ably::Rest::Channels#get)
|
|
278
|
+
def channel(name, channel_options = {})
|
|
279
|
+
channels.get(name, channel_options)
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
# Queries the REST /stats API and retrieves your application's usage statistics. Returns a {Ably::Models::PaginatedResult} object, containing an array of {Ably::Models::Stats} objects. See the Stats docs.
|
|
283
|
+
#
|
|
284
|
+
# @spec RSC6a, RSC6b1, RSC6b2, RSC6b3, RSC6b4
|
|
285
|
+
#
|
|
286
|
+
# @param [Hash] options the options for the stats request
|
|
287
|
+
# @option options [Integer,Time] :start The time from which stats are retrieved, specified as milliseconds since the Unix epoch. RSC6b1
|
|
288
|
+
# @option options [Integer,Time] :end The time until stats are retrieved, specified as milliseconds since the Unix epoch. RSC6b1
|
|
289
|
+
# @option options [Symbol] :direction The order for which stats are returned in. Valid values are backwards which orders stats from most recent to oldest, or forwards which orders stats from oldest to most recent. The default is backwards. RSC6b2
|
|
290
|
+
# @option options [Integer] :limit An upper limit on the number of stats returned. The default is 100, and the maximum is 1000. RSC6b3
|
|
291
|
+
# @option options [Symbol] :unit minute, hour, day or month. Based on the unit selected, the given start or end times are rounded down to the start of the relevant interval depending on the unit granularity of the query. RSC6b4
|
|
292
|
+
#
|
|
293
|
+
# @return [Ably::Models::PaginatedResult<Ably::Models::Stats>] A {Ably::Models::PaginatedResult} object containing an array of {Ably::Models::Stats} objects.
|
|
294
|
+
#
|
|
295
|
+
def stats(options = {})
|
|
296
|
+
options = {
|
|
297
|
+
:direction => :backwards,
|
|
298
|
+
:unit => :minute,
|
|
299
|
+
:limit => 100
|
|
300
|
+
}.merge(options)
|
|
301
|
+
|
|
302
|
+
[:start, :end].each { |option| options[option] = as_since_epoch(options[option]) if options.has_key?(option) }
|
|
303
|
+
raise ArgumentError, ":end must be equal to or after :start" if options[:start] && options[:end] && (options[:start] > options[:end])
|
|
304
|
+
|
|
305
|
+
paginated_options = {
|
|
306
|
+
coerce_into: 'Ably::Models::Stats'
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
url = '/stats'
|
|
310
|
+
response = get(url, options)
|
|
311
|
+
|
|
312
|
+
Ably::Models::PaginatedResult.new(response, url, self, paginated_options)
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
# Retrieves the time from the Ably service as milliseconds since the Unix epoch. Clients that do not have access
|
|
316
|
+
# to a sufficiently well maintained time source and wish to issue Ably {Ably::Models::TokenRequest} with
|
|
317
|
+
# a more accurate timestamp should use the {Ably::Rest::Client#queryTime} property instead of this method.
|
|
318
|
+
#
|
|
319
|
+
# @spec RSC16
|
|
320
|
+
#
|
|
321
|
+
# @return [Time] The time as milliseconds since the Unix epoch.
|
|
322
|
+
def time
|
|
323
|
+
response = get('/time', {}, send_auth_header: false)
|
|
324
|
+
|
|
325
|
+
as_time_from_epoch(response.body.first)
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
# @!attribute [r] use_tls?
|
|
329
|
+
# @return [Boolean] True if client is configured to use TLS for all Ably communication
|
|
330
|
+
def use_tls?
|
|
331
|
+
@tls == true
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
# Perform an HTTP GET request to the API using configured authentication
|
|
335
|
+
#
|
|
336
|
+
# @return [Faraday::Response]
|
|
337
|
+
#
|
|
338
|
+
# @api private
|
|
339
|
+
def get(path, params = {}, options = {})
|
|
340
|
+
raw_request(:get, path, params, options)
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
# Perform an HTTP POST request to the API using configured authentication
|
|
344
|
+
#
|
|
345
|
+
# @return [Faraday::Response]
|
|
346
|
+
#
|
|
347
|
+
# @api private
|
|
348
|
+
def post(path, params, options = {})
|
|
349
|
+
raw_request(:post, path, params, options)
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
# Perform an HTTP PUT request to the API using configured authentication
|
|
353
|
+
#
|
|
354
|
+
# @return [Faraday::Response]
|
|
355
|
+
#
|
|
356
|
+
# @api private
|
|
357
|
+
def put(path, params, options = {})
|
|
358
|
+
raw_request(:put, path, params, options)
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
# Perform an HTTP DELETE request to the API using configured authentication
|
|
362
|
+
#
|
|
363
|
+
# @return [Faraday::Response]
|
|
364
|
+
#
|
|
365
|
+
# @api private
|
|
366
|
+
def delete(path, params, options = {})
|
|
367
|
+
raw_request(:delete, path, params, options)
|
|
368
|
+
end
|
|
369
|
+
|
|
370
|
+
# Makes a REST request to a provided path. This is provided as a convenience for developers who wish to use REST API
|
|
371
|
+
# functionality that is either not documented or is not yet included in the public API, without having to directly
|
|
372
|
+
# handle features such as authentication, paging, fallback hosts, MsgPack and JSON support.
|
|
373
|
+
#
|
|
374
|
+
# @spec RSC19
|
|
375
|
+
#
|
|
376
|
+
# @param method [Symbol] The request method to use, such as :get, :post.
|
|
377
|
+
# @param path [String] The request path.
|
|
378
|
+
# @param params [Hash, nil] The parameters to include in the URL query of the request. The parameters depend on the endpoint being queried. See the REST API reference for the available parameters of each endpoint.
|
|
379
|
+
# @param body [Hash, nil] The JSON body of the request.
|
|
380
|
+
# @param headers [Hash, nil] Additional HTTP headers to include in the request.
|
|
381
|
+
#
|
|
382
|
+
# @return [Ably::Models::HttpPaginatedResponse<>] An {Ably::Models::HttpPaginatedResponse} object returned by the HTTP request, containing an empty or JSON-encodable object.
|
|
383
|
+
def request(method, path, params = {}, body = nil, headers = {}, options = {})
|
|
384
|
+
raise "Method #{method.to_s.upcase} not supported" unless %i(get put patch post delete).include?(method.to_sym)
|
|
385
|
+
|
|
386
|
+
response = case method.to_sym
|
|
387
|
+
when :get, :delete
|
|
388
|
+
reauthorize_on_authorization_failure do
|
|
389
|
+
send_request(method, path, params, headers: headers)
|
|
390
|
+
end
|
|
391
|
+
when :post, :patch, :put
|
|
392
|
+
if body.to_json.bytesize > max_frame_size
|
|
393
|
+
raise Ably::Exceptions::MaxFrameSizeExceeded.new("Maximum frame size exceeded #{max_frame_size} bytes.")
|
|
394
|
+
end
|
|
395
|
+
path_with_params = Addressable::URI.new
|
|
396
|
+
path_with_params.query_values = params || {}
|
|
397
|
+
query = path_with_params.query
|
|
398
|
+
reauthorize_on_authorization_failure do
|
|
399
|
+
send_request(method, "#{path}#{"?#{query}" unless query.nil? || query.empty?}", body, headers: headers)
|
|
400
|
+
end
|
|
401
|
+
end
|
|
402
|
+
|
|
403
|
+
paginated_options = {
|
|
404
|
+
async_blocking_operations: options.delete(:async_blocking_operations),
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
Ably::Models::HttpPaginatedResponse.new(response, path, self, paginated_options)
|
|
408
|
+
|
|
409
|
+
rescue Exceptions::ResourceMissing, Exceptions::ForbiddenRequest, Exceptions::ResourceMissing => e
|
|
410
|
+
response = Models::HttpPaginatedResponse::ErrorResponse.new(e.status, e.code, e.message)
|
|
411
|
+
Models::HttpPaginatedResponse.new(response, path, self)
|
|
412
|
+
rescue Exceptions::TokenExpired, Exceptions::UnauthorizedRequest => e
|
|
413
|
+
response = Models::HttpPaginatedResponse::ErrorResponse.new(e.status, e.code, e.message)
|
|
414
|
+
Models::HttpPaginatedResponse.new(response, path, self)
|
|
415
|
+
rescue Exceptions::InvalidRequest, Exceptions::ServerError => e
|
|
416
|
+
response = Models::HttpPaginatedResponse::ErrorResponse.new(e.status, e.code, e.message)
|
|
417
|
+
Models::HttpPaginatedResponse.new(response, path, self)
|
|
418
|
+
end
|
|
419
|
+
|
|
420
|
+
# Retrieves an object that represents the current state of the device as a target for push notifications.
|
|
421
|
+
# @spec RSH8
|
|
422
|
+
# @return [Ably::Models::LocalDevice]
|
|
423
|
+
#
|
|
424
|
+
# @note This is unsupported in the Ruby library
|
|
425
|
+
def device
|
|
426
|
+
raise Ably::Exceptions::PushNotificationsNotSupported, 'This device does not support receiving or subscribing to push notifications. The local device object is not unavailable'
|
|
427
|
+
end
|
|
428
|
+
|
|
429
|
+
# A {Ably::Rest::Push} object.
|
|
430
|
+
# @spec RSH7
|
|
431
|
+
# @return [Ably::Rest::Push]
|
|
432
|
+
def push
|
|
433
|
+
@push ||= Push.new(self)
|
|
434
|
+
end
|
|
435
|
+
|
|
436
|
+
# @!attribute [r] endpoint
|
|
437
|
+
# @return [URI::Generic] Default Ably REST endpoint used for all requests
|
|
438
|
+
def endpoint
|
|
439
|
+
endpoint_for_host(custom_host || [@environment, DOMAIN].compact.join('-'))
|
|
440
|
+
end
|
|
441
|
+
|
|
442
|
+
# @!attribute [r] logger
|
|
443
|
+
# @return [Logger] The {Ably::Logger} for this client.
|
|
444
|
+
# Configure the log_level with the `:log_level` option, refer to {Client#initialize}
|
|
445
|
+
def logger
|
|
446
|
+
@logger ||= Ably::Logger.new(self, log_level, @custom_logger)
|
|
447
|
+
end
|
|
448
|
+
|
|
449
|
+
# @!attribute [r] mime_type
|
|
450
|
+
# @return [String] Mime type used for HTTP requests
|
|
451
|
+
def mime_type
|
|
452
|
+
case protocol
|
|
453
|
+
when :json
|
|
454
|
+
'application/json'
|
|
455
|
+
else
|
|
456
|
+
'application/x-msgpack'
|
|
457
|
+
end
|
|
458
|
+
end
|
|
459
|
+
|
|
460
|
+
# Register a message encoder and decoder that implements Ably::Models::MessageEncoders::Base interface.
|
|
461
|
+
# Message encoders are used to encode and decode message payloads automatically.
|
|
462
|
+
# @note Encoders and decoders are processed in the order they are added so the first encoder will be given priority when encoding and decoding
|
|
463
|
+
#
|
|
464
|
+
# @param [Ably::Models::MessageEncoders::Base] encoder
|
|
465
|
+
# @return [void]
|
|
466
|
+
#
|
|
467
|
+
# @api private
|
|
468
|
+
def register_encoder(encoder, options = {})
|
|
469
|
+
encoders << Ably::Models::MessageEncoders.encoder_from(encoder, options)
|
|
470
|
+
end
|
|
471
|
+
|
|
472
|
+
# @!attribute [r] protocol_binary?
|
|
473
|
+
# @return [Boolean] True of the transport #protocol communicates with Ably with a binary protocol
|
|
474
|
+
def protocol_binary?
|
|
475
|
+
protocol == :msgpack
|
|
476
|
+
end
|
|
477
|
+
|
|
478
|
+
# Connection used to make HTTP requests
|
|
479
|
+
#
|
|
480
|
+
# @param [Hash] options
|
|
481
|
+
# @option options [Boolean] :use_fallback when true, one of the fallback connections is used randomly, see the default {Ably::FALLBACK_HOSTS}
|
|
482
|
+
#
|
|
483
|
+
# @return [Faraday::Connection]
|
|
484
|
+
#
|
|
485
|
+
# @api private
|
|
486
|
+
def connection(options = {})
|
|
487
|
+
if options[:use_fallback]
|
|
488
|
+
fallback_connection
|
|
489
|
+
else
|
|
490
|
+
@connection ||= Faraday.new(endpoint.to_s, connection_options)
|
|
491
|
+
end
|
|
492
|
+
end
|
|
493
|
+
|
|
494
|
+
# Fallback connection used to make HTTP requests.
|
|
495
|
+
# Note, each request uses a random and then subsequent random {Ably::FALLBACK_HOSTS fallback hosts}
|
|
496
|
+
# are used (unless custom fallback hosts are provided with fallback_hosts)
|
|
497
|
+
#
|
|
498
|
+
# @return [Faraday::Connection]
|
|
499
|
+
#
|
|
500
|
+
# @api private
|
|
501
|
+
def fallback_connection
|
|
502
|
+
unless defined?(@fallback_connections) && @fallback_connections
|
|
503
|
+
@fallback_connections = fallback_hosts.shuffle.map { |host| Faraday.new(endpoint_for_host(host).to_s, connection_options) }
|
|
504
|
+
end
|
|
505
|
+
@fallback_index ||= 0
|
|
506
|
+
|
|
507
|
+
@fallback_connections[@fallback_index % @fallback_connections.count].tap do
|
|
508
|
+
@fallback_index += 1
|
|
509
|
+
end
|
|
510
|
+
end
|
|
511
|
+
|
|
512
|
+
# Allowable duration for an external auth request
|
|
513
|
+
# For REST client this defaults to request_timeout
|
|
514
|
+
# For Realtime clients this defaults to 250ms less than the realtime_request_timeout
|
|
515
|
+
# ensuring an auth failure will be triggered before the realtime request timeout fires
|
|
516
|
+
# which would lead to a misleading error message (connection timeout as opposed to auth request timeout)
|
|
517
|
+
# @api private
|
|
518
|
+
def auth_request_timeout
|
|
519
|
+
if @realtime_client
|
|
520
|
+
@realtime_client.connection.defaults.fetch(:realtime_request_timeout) - 0.25
|
|
521
|
+
else
|
|
522
|
+
http_defaults.fetch(:request_timeout)
|
|
523
|
+
end
|
|
524
|
+
end
|
|
525
|
+
|
|
526
|
+
# If the primary host endpoint fails, and a subsequent fallback host succeeds, the fallback
|
|
527
|
+
# host that succeeded is used for +ClientOption+ +fallback_retry_timeout+ seconds to avoid
|
|
528
|
+
# retries to known failing hosts for a short period of time.
|
|
529
|
+
# See https://github.com/ably/docs/pull/554, spec id #RSC15f
|
|
530
|
+
#
|
|
531
|
+
# @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
|
|
532
|
+
def using_preferred_fallback_host?
|
|
533
|
+
if preferred_fallback_connection && (preferred_fallback_connection.fetch(:expires_at) > Time.now)
|
|
534
|
+
preferred_fallback_connection.fetch(:connection_object).host
|
|
535
|
+
end
|
|
536
|
+
end
|
|
537
|
+
|
|
538
|
+
private
|
|
539
|
+
|
|
540
|
+
attr_reader :preferred_fallback_connection
|
|
541
|
+
|
|
542
|
+
# See #using_preferred_fallback_host? for context
|
|
543
|
+
def set_preferred_fallback_connection(connection)
|
|
544
|
+
@preferred_fallback_connection = if connection == @connection
|
|
545
|
+
# If the succeeded connection is in fact the primary connection (tried after a failed fallback)
|
|
546
|
+
# then clear the preferred fallback connection
|
|
547
|
+
nil
|
|
548
|
+
else
|
|
549
|
+
{
|
|
550
|
+
expires_at: Time.now + options.fetch(:fallback_retry_timeout),
|
|
551
|
+
connection_object: connection,
|
|
552
|
+
}
|
|
553
|
+
end
|
|
554
|
+
end
|
|
555
|
+
|
|
556
|
+
def get_preferred_fallback_connection_object
|
|
557
|
+
preferred_fallback_connection.fetch(:connection_object) if using_preferred_fallback_host?
|
|
558
|
+
end
|
|
559
|
+
|
|
560
|
+
def raw_request(method, path, params = {}, options = {})
|
|
561
|
+
options = options.clone
|
|
562
|
+
if options.delete(:disable_automatic_reauthorize) == true
|
|
563
|
+
send_request(method, path, params, options)
|
|
564
|
+
else
|
|
565
|
+
reauthorize_on_authorization_failure do
|
|
566
|
+
send_request(method, path, params, options)
|
|
567
|
+
end
|
|
568
|
+
end
|
|
569
|
+
end
|
|
570
|
+
|
|
571
|
+
# Sends HTTP request to connection end point
|
|
572
|
+
# Connection failures will automatically be reattempted until thresholds are met
|
|
573
|
+
def send_request(method, path, params, options)
|
|
574
|
+
max_retry_count = http_defaults.fetch(:max_retry_count)
|
|
575
|
+
max_retry_duration = http_defaults.fetch(:max_retry_duration)
|
|
576
|
+
requested_at = Time.now
|
|
577
|
+
retry_count = 0
|
|
578
|
+
retry_sequence_id = nil
|
|
579
|
+
request_id = SecureRandom.urlsafe_base64(10) if add_request_ids
|
|
580
|
+
|
|
581
|
+
preferred_fallback_connection_for_first_request = get_preferred_fallback_connection_object
|
|
582
|
+
|
|
583
|
+
begin
|
|
584
|
+
use_fallback = can_fallback_to_alternate_ably_host? && (retry_count > 0)
|
|
585
|
+
|
|
586
|
+
conn = if preferred_fallback_connection_for_first_request
|
|
587
|
+
case retry_count
|
|
588
|
+
when 0
|
|
589
|
+
preferred_fallback_connection_for_first_request
|
|
590
|
+
when 1
|
|
591
|
+
# Ensure the root host is used first if the preferred fallback fails, see #RSC15f
|
|
592
|
+
connection(use_fallback: false)
|
|
593
|
+
end
|
|
594
|
+
end || connection(use_fallback: use_fallback) # default to normal connection selection process if not preferred connection set
|
|
595
|
+
|
|
596
|
+
conn.send(method, path, params) do |request|
|
|
597
|
+
if add_request_ids
|
|
598
|
+
request.params[:request_id] = request_id
|
|
599
|
+
request.options.context = {} if request.options.context.nil?
|
|
600
|
+
request.options.context[:request_id] = request_id
|
|
601
|
+
end
|
|
602
|
+
if options[:qs_params]
|
|
603
|
+
request.params.merge!(options[:qs_params])
|
|
604
|
+
end
|
|
605
|
+
unless options[:send_auth_header] == false
|
|
606
|
+
request.headers[:authorization] = auth.auth_header
|
|
607
|
+
# RSA7e2
|
|
608
|
+
options[:headers].to_h.merge(auth.extra_auth_headers).map do |key, val|
|
|
609
|
+
request.headers[key] = val
|
|
610
|
+
end
|
|
611
|
+
end
|
|
612
|
+
end.tap do
|
|
613
|
+
if retry_count > 0
|
|
614
|
+
retry_log_severity = log_retries_as_info ? :info : :warn
|
|
615
|
+
logger.public_send(retry_log_severity) do
|
|
616
|
+
"Ably::Rest::Client - Request SUCCEEDED after #{retry_count} #{retry_count > 1 ? 'retries' : 'retry' } for" \
|
|
617
|
+
" #{method} #{path} #{params} (seq ##{retry_sequence_id}, time elapsed #{(Time.now.to_f - requested_at.to_f).round(2)}s)"
|
|
618
|
+
end
|
|
619
|
+
set_preferred_fallback_connection conn
|
|
620
|
+
end
|
|
621
|
+
end
|
|
622
|
+
|
|
623
|
+
rescue *([Faraday::TimeoutError, Ably::Exceptions::ServerError] + FARADAY_CLIENT_OR_SERVER_ERRORS) => error
|
|
624
|
+
retry_sequence_id ||= SecureRandom.urlsafe_base64(4)
|
|
625
|
+
time_passed = Time.now - requested_at
|
|
626
|
+
|
|
627
|
+
if can_fallback_to_alternate_ably_host? && (retry_count < max_retry_count) && (time_passed <= max_retry_duration)
|
|
628
|
+
retry_count += 1
|
|
629
|
+
retry_log_severity = log_retries_as_info ? :info : :warn
|
|
630
|
+
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}" }
|
|
631
|
+
retry
|
|
632
|
+
end
|
|
633
|
+
|
|
634
|
+
retry_log_severity = log_retries_as_info ? :info : :error
|
|
635
|
+
logger.public_send(retry_log_severity) do
|
|
636
|
+
"Ably::Rest::Client - Request FAILED after #{retry_count} #{retry_count > 1 ? 'retries' : 'retry' } for" \
|
|
637
|
+
" #{method} #{path} #{params} (seq ##{retry_sequence_id}, time elapsed #{(Time.now.to_f - requested_at.to_f).round(2)}s)"
|
|
638
|
+
end
|
|
639
|
+
|
|
640
|
+
case error
|
|
641
|
+
when Faraday::TimeoutError
|
|
642
|
+
raise Ably::Exceptions::ConnectionTimeout.new(error.message, nil, Ably::Exceptions::Codes::CONNECTION_TIMED_OUT, error, { request_id: request_id })
|
|
643
|
+
when *FARADAY_CLIENT_OR_SERVER_ERRORS
|
|
644
|
+
# request_id is also available in the request context
|
|
645
|
+
raise Ably::Exceptions::ConnectionError.new(error.message, nil, Ably::Exceptions::Codes::CONNECTION_FAILED, error, { request_id: request_id })
|
|
646
|
+
else
|
|
647
|
+
raise error
|
|
648
|
+
end
|
|
649
|
+
end
|
|
650
|
+
end
|
|
651
|
+
|
|
652
|
+
def reauthorize_on_authorization_failure
|
|
653
|
+
yield
|
|
654
|
+
rescue Ably::Exceptions::TokenExpired => e
|
|
655
|
+
if auth.token_renewable?
|
|
656
|
+
auth.authorize
|
|
657
|
+
yield
|
|
658
|
+
else
|
|
659
|
+
raise e
|
|
660
|
+
end
|
|
661
|
+
end
|
|
662
|
+
|
|
663
|
+
def endpoint_for_host(host)
|
|
664
|
+
port = if use_tls?
|
|
665
|
+
custom_tls_port
|
|
666
|
+
else
|
|
667
|
+
custom_port
|
|
668
|
+
end
|
|
669
|
+
|
|
670
|
+
raise ArgumentError, "Custom port must be an Integer or nil" if port && !port.kind_of?(Integer)
|
|
671
|
+
|
|
672
|
+
options = {
|
|
673
|
+
scheme: use_tls? ? 'https' : 'http',
|
|
674
|
+
host: host
|
|
675
|
+
}
|
|
676
|
+
options.merge!(port: port) if port
|
|
677
|
+
|
|
678
|
+
URI::Generic.build(options)
|
|
679
|
+
end
|
|
680
|
+
|
|
681
|
+
# Return a Hash of connection options to initiate the Faraday::Connection with
|
|
682
|
+
#
|
|
683
|
+
# @return [Hash]
|
|
684
|
+
def connection_options
|
|
685
|
+
@connection_options ||= {
|
|
686
|
+
builder: middleware,
|
|
687
|
+
headers: {
|
|
688
|
+
content_type: mime_type,
|
|
689
|
+
accept: mime_type,
|
|
690
|
+
user_agent: user_agent,
|
|
691
|
+
'X-Ably-Version' => Ably::PROTOCOL_VERSION,
|
|
692
|
+
'Ably-Agent' => agent
|
|
693
|
+
},
|
|
694
|
+
request: {
|
|
695
|
+
open_timeout: http_defaults.fetch(:open_timeout),
|
|
696
|
+
timeout: http_defaults.fetch(:request_timeout)
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
end
|
|
700
|
+
|
|
701
|
+
# Return a Faraday middleware stack to initiate the Faraday::RackBuilder with
|
|
702
|
+
#
|
|
703
|
+
# @see http://mislav.uniqpath.com/2011/07/faraday-advanced-http/
|
|
704
|
+
def middleware
|
|
705
|
+
@middleware ||= Faraday::RackBuilder.new do |builder|
|
|
706
|
+
setup_outgoing_middleware builder
|
|
707
|
+
|
|
708
|
+
# Raise exceptions if response code is invalid
|
|
709
|
+
builder.use Ably::Rest::Middleware::Exceptions
|
|
710
|
+
|
|
711
|
+
setup_incoming_middleware builder, logger, fail_if_unsupported_mime_type: true
|
|
712
|
+
|
|
713
|
+
# Set Faraday's HTTP adapter with support for HTTP/2
|
|
714
|
+
builder.adapter :typhoeus, http_version: :httpv2_0
|
|
715
|
+
end
|
|
716
|
+
end
|
|
717
|
+
|
|
718
|
+
def can_fallback_to_alternate_ably_host?
|
|
719
|
+
fallback_hosts && !fallback_hosts.empty?
|
|
720
|
+
end
|
|
721
|
+
|
|
722
|
+
def initialize_default_encoders
|
|
723
|
+
Ably::Models::MessageEncoders.register_default_encoders self, binary_protocol: protocol == :msgpack
|
|
724
|
+
end
|
|
725
|
+
end
|
|
726
|
+
end
|
|
727
|
+
end
|