ably 0.8.13 → 0.8.14
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +17 -1
- data/lib/ably.rb +2 -1
- data/lib/ably/exceptions.rb +6 -1
- data/lib/ably/realtime/client.rb +9 -3
- data/lib/ably/realtime/connection.rb +36 -8
- data/lib/ably/realtime/connection/connection_manager.rb +1 -1
- data/lib/ably/rest/client.rb +45 -10
- data/lib/ably/version.rb +13 -2
- data/spec/acceptance/realtime/connection_failures_spec.rb +185 -38
- data/spec/acceptance/realtime/connection_spec.rb +55 -12
- data/spec/acceptance/rest/client_spec.rb +308 -15
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b7ff0fc1dc14fc84ea707c200752a84943571d53
|
4
|
+
data.tar.gz: 7ba75aa7a6bfdacd097ffa694409bb172711eddb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8eaf2f813c5003c87c64c0f9dbd33e6231ac1d9c1fbd2a1e07117f566c648b6eed4fd50eb4ec15e622bf510dc225e70cd3d6a42dfe25ca4da8ca69529d5001f4
|
7
|
+
data.tar.gz: f07522639b0068f1a1aea116d04bcfbc745d7d9862ad12ea64111921854c1da4eb785480f2c5881083c0aa30c052ce1741552ad8b8195c7fd75f4868df58a556
|
data/CHANGELOG.md
CHANGED
@@ -1,6 +1,22 @@
|
|
1
1
|
# Change Log
|
2
2
|
|
3
|
-
## [Unreleased](https://github.com/ably/ably-ruby/tree/v0.8.
|
3
|
+
## [Unreleased](https://github.com/ably/ably-ruby/tree/v0.8.14)
|
4
|
+
|
5
|
+
[Full Changelog](https://github.com/ably/ably-ruby/compare/v0.8.13...v0.8.14)
|
6
|
+
|
7
|
+
**Fixed bugs:**
|
8
|
+
|
9
|
+
- Several problems with fallback hosts [\#95](https://github.com/ably/ably-ruby/issues/95)
|
10
|
+
|
11
|
+
**Closed issues:**
|
12
|
+
|
13
|
+
- Catch common SSL error and show docs [\#96](https://github.com/ably/ably-ruby/issues/96)
|
14
|
+
|
15
|
+
**Merged pull requests:**
|
16
|
+
|
17
|
+
- Fallback host improvements [\#97](https://github.com/ably/ably-ruby/pull/97) ([mattheworiordan](https://github.com/mattheworiordan))
|
18
|
+
|
19
|
+
## [v0.8.13](https://github.com/ably/ably-ruby/tree/v0.8.13) (2016-09-29)
|
4
20
|
|
5
21
|
[Full Changelog](https://github.com/ably/ably-ruby/compare/v0.8.12...v0.8.13)
|
6
22
|
|
data/lib/ably.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
require 'addressable/uri'
|
2
2
|
|
3
|
+
require 'ably/version'
|
4
|
+
|
3
5
|
%w(modules util).each do |namespace|
|
4
6
|
Dir.glob(File.expand_path("ably/#{namespace}/*.rb", File.dirname(__FILE__))).sort.each do |file|
|
5
7
|
require file
|
@@ -11,4 +13,3 @@ require 'ably/exceptions'
|
|
11
13
|
require 'ably/logger'
|
12
14
|
require 'ably/realtime'
|
13
15
|
require 'ably/rest'
|
14
|
-
require 'ably/version'
|
data/lib/ably/exceptions.rb
CHANGED
@@ -59,7 +59,12 @@ module Ably
|
|
59
59
|
|
60
60
|
def to_s
|
61
61
|
message = [super]
|
62
|
-
|
62
|
+
if @base_error
|
63
|
+
message << "#{@base_error}"
|
64
|
+
if @base_error.respond_to?(:message) && @base_error.message.match(/certificate verify failed/i)
|
65
|
+
message << "See https://goo.gl/eKvfcR to resolve this issue."
|
66
|
+
end
|
67
|
+
end
|
63
68
|
message.join(' < ')
|
64
69
|
end
|
65
70
|
end
|
data/lib/ably/realtime/client.rb
CHANGED
@@ -155,6 +155,11 @@ module Ably
|
|
155
155
|
rest_client.register_encoder encoder
|
156
156
|
end
|
157
157
|
|
158
|
+
# (see Ably::Rest::Client#fallback_hosts)
|
159
|
+
def fallback_hosts
|
160
|
+
rest_client.fallback_hosts
|
161
|
+
end
|
162
|
+
|
158
163
|
# (see Ably::Rest::Client#logger)
|
159
164
|
def logger
|
160
165
|
@logger ||= Ably::Logger.new(self, log_level, rest_client.logger.custom_logger)
|
@@ -168,14 +173,15 @@ module Ably
|
|
168
173
|
end
|
169
174
|
|
170
175
|
# @!attribute [r] fallback_endpoint
|
171
|
-
# @return [URI::Generic] Fallback endpoint used to connect to the realtime Ably service. Note, after each connection attempt, a new random {Ably::FALLBACK_HOSTS fallback host}
|
176
|
+
# @return [URI::Generic] Fallback endpoint used to connect to the realtime Ably service. Note, after each connection attempt, a new random {Ably::FALLBACK_HOSTS fallback host} or provided fallback hosts are used
|
172
177
|
# @api private
|
173
178
|
def fallback_endpoint
|
174
179
|
unless defined?(@fallback_endpoints) && @fallback_endpoints
|
175
|
-
@fallback_endpoints =
|
180
|
+
@fallback_endpoints = fallback_hosts.shuffle.map { |fallback_host| endpoint_for_host(fallback_host) }
|
181
|
+
@fallback_endpoints << endpoint # Try the original host last if all fallbacks have been used
|
176
182
|
end
|
177
183
|
|
178
|
-
fallback_endpoint_index = connection.manager.retry_count_for_state(:disconnected) + connection.manager.retry_count_for_state(:suspended)
|
184
|
+
fallback_endpoint_index = connection.manager.retry_count_for_state(:disconnected) + connection.manager.retry_count_for_state(:suspended) - 1
|
179
185
|
|
180
186
|
@fallback_endpoints[fallback_endpoint_index % @fallback_endpoints.count]
|
181
187
|
end
|
@@ -64,8 +64,9 @@ module Ably
|
|
64
64
|
DEFAULTS = {
|
65
65
|
disconnected_retry_timeout: 15, # when the connection enters the DISCONNECTED state, after this delay in milliseconds, if the state is still DISCONNECTED, the client library will attempt to reconnect automatically
|
66
66
|
suspended_retry_timeout: 30, # when the connection enters the SUSPENDED state, after this delay in milliseconds, if the state is still SUSPENDED, the client library will attempt to reconnect automatically
|
67
|
-
connection_state_ttl:
|
68
|
-
|
67
|
+
connection_state_ttl: 120, # the duration that Ably will persist the connection state when a Realtime client is abruptly disconnected
|
68
|
+
max_connection_state_ttl: nil, # allow a max TTL to be passed in for CI test purposes thus overiding any connection_state_ttl sent from Ably
|
69
|
+
realtime_request_timeout: 10, # default timeout when establishing a connection, or sending a HEARTBEAT, CONNECT, ATTACH, DETACH or CLOSE ProtocolMessage
|
69
70
|
}.freeze
|
70
71
|
|
71
72
|
# A unique public identifier for this connection, used to identify this member in presence events and messages
|
@@ -389,7 +390,7 @@ module Ably
|
|
389
390
|
format: client.protocol,
|
390
391
|
echo: client.echo_messages,
|
391
392
|
v: Ably::PROTOCOL_VERSION,
|
392
|
-
lib:
|
393
|
+
lib: client.rest_client.lib_version_id,
|
393
394
|
)
|
394
395
|
|
395
396
|
url_params['clientId'] = client.auth.client_id if client.auth.has_client_id?
|
@@ -407,12 +408,12 @@ module Ably
|
|
407
408
|
|
408
409
|
url = URI(client.endpoint).tap do |endpoint|
|
409
410
|
endpoint.query = URI.encode_www_form(url_params)
|
410
|
-
end
|
411
|
+
end
|
411
412
|
|
412
413
|
determine_host do |host|
|
413
414
|
begin
|
414
|
-
logger.debug "Connection: Opening socket connection to #{host}:#{port}
|
415
|
-
@transport =
|
415
|
+
logger.debug "Connection: Opening socket connection to #{host}:#{port}/#{url.path}?#{url.query}"
|
416
|
+
@transport = create_transport(host, port, url) do |websocket_transport|
|
416
417
|
websocket_deferrable.succeed websocket_transport
|
417
418
|
end
|
418
419
|
rescue EventMachine::ConnectionError => error
|
@@ -473,6 +474,22 @@ module Ably
|
|
473
474
|
( (initialized? || connecting? || disconnected?) && client.queue_messages )
|
474
475
|
end
|
475
476
|
|
477
|
+
# @api private
|
478
|
+
def create_transport(host, port, url, &block)
|
479
|
+
EventMachine.connect(host, port, WebsocketTransport, self, url.to_s, &block)
|
480
|
+
end
|
481
|
+
|
482
|
+
# @api private
|
483
|
+
def connection_state_ttl
|
484
|
+
defaults[:max_connection_state_ttl] || # undocumented max TTL configuration
|
485
|
+
(details && details.connection_state_ttl) ||
|
486
|
+
defaults.fetch(:connection_state_ttl)
|
487
|
+
end
|
488
|
+
|
489
|
+
def connection_state_ttl=(val)
|
490
|
+
@connection_state_ttl = val
|
491
|
+
end
|
492
|
+
|
476
493
|
# As we are using a state machine, do not allow change_state to be used
|
477
494
|
# #transition_state_machine must be used instead
|
478
495
|
private :change_state
|
@@ -527,7 +544,18 @@ module Ably
|
|
527
544
|
end
|
528
545
|
|
529
546
|
def connection_resumable?
|
530
|
-
!key.nil? && !serial.nil?
|
547
|
+
!key.nil? && !serial.nil? && connection_state_available?
|
548
|
+
end
|
549
|
+
|
550
|
+
def connection_state_available?
|
551
|
+
return true if connected?
|
552
|
+
|
553
|
+
connected_last = state_history.reverse.find { |connected| connected.fetch(:state) == :connected }
|
554
|
+
if connected_last.nil? || (connected_last.fetch(:transitioned_at) < Time.now - connection_state_ttl)
|
555
|
+
false
|
556
|
+
else
|
557
|
+
true
|
558
|
+
end
|
531
559
|
end
|
532
560
|
|
533
561
|
def connection_recoverable?
|
@@ -555,7 +583,7 @@ module Ably
|
|
555
583
|
end
|
556
584
|
|
557
585
|
def can_use_fallback_hosts?
|
558
|
-
if
|
586
|
+
if client.fallback_hosts && !client.fallback_hosts.empty?
|
559
587
|
if connecting? && previous_state
|
560
588
|
use_fallback_if_disconnected? || use_fallback_if_suspended?
|
561
589
|
end
|
@@ -320,7 +320,7 @@ module Ably::Realtime
|
|
320
320
|
def can_reattempt_connect_for_state?(state)
|
321
321
|
case state
|
322
322
|
when :disconnected
|
323
|
-
time_spent_attempting_state(:disconnected, ignore_states: [:connecting]) < connection.
|
323
|
+
time_spent_attempting_state(:disconnected, ignore_states: [:connecting]) < connection.connection_state_ttl
|
324
324
|
when :suspended
|
325
325
|
true # suspended state remains indefinitely
|
326
326
|
else
|
data/lib/ably/rest/client.rb
CHANGED
@@ -78,10 +78,14 @@ module Ably
|
|
78
78
|
# @api private
|
79
79
|
attr_reader :options
|
80
80
|
|
81
|
+
# The list of fallback hosts to be used by this client
|
82
|
+
# if empty or nil then fallback host functionality is disabled
|
83
|
+
attr_reader :fallback_hosts
|
84
|
+
|
81
85
|
# Creates a {Ably::Rest::Client Rest Client} and configures the {Ably::Auth} object for the connection.
|
82
86
|
#
|
83
87
|
# @param [Hash,String] options an options Hash used to configure the client and the authentication, or String with an API key or Token ID
|
84
|
-
# @option options [Boolean] :tls
|
88
|
+
# @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.
|
85
89
|
# @option options [String] :key API key comprising the key name and key secret in a single string
|
86
90
|
# @option options [String] :token Token string or {Models::TokenDetails} used to authenticate requests
|
87
91
|
# @option options [String] :token_details {Models::TokenDetails} used to authenticate requests
|
@@ -106,6 +110,9 @@ module Ably
|
|
106
110
|
# @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
|
107
111
|
# @option options [Integer] :http_max_retry_duration (10 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
|
108
112
|
#
|
113
|
+
# @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
|
114
|
+
# @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
|
115
|
+
#
|
109
116
|
# @return [Ably::Rest::Client]
|
110
117
|
#
|
111
118
|
# @example
|
@@ -129,6 +136,7 @@ module Ably
|
|
129
136
|
|
130
137
|
@tls = options.delete(:tls) == false ? false : true
|
131
138
|
@environment = options.delete(:environment) # nil is production
|
139
|
+
@environment = nil if [:production, 'production'].include?(@environment)
|
132
140
|
@protocol = options.delete(:protocol) || :msgpack
|
133
141
|
@debug_http = options.delete(:debug_http)
|
134
142
|
@log_level = options.delete(:log_level) || ::Logger::WARN
|
@@ -137,6 +145,20 @@ module Ably
|
|
137
145
|
@custom_port = options.delete(:port)
|
138
146
|
@custom_tls_port = options.delete(:tls_port)
|
139
147
|
|
148
|
+
if options[:fallback_hosts_use_default] && options[:fallback_jhosts]
|
149
|
+
raise ArgumentError, "fallback_hosts_use_default cannot be set to trye when fallback_jhosts is also provided"
|
150
|
+
end
|
151
|
+
@fallback_hosts = case
|
152
|
+
when options.delete(:fallback_hosts_use_default)
|
153
|
+
Ably::FALLBACK_HOSTS
|
154
|
+
when options_fallback_hosts = options.delete(:fallback_hosts)
|
155
|
+
options_fallback_hosts
|
156
|
+
when environment || custom_host || options[:realtime_host] || custom_port || custom_tls_port
|
157
|
+
[]
|
158
|
+
else
|
159
|
+
Ably::FALLBACK_HOSTS
|
160
|
+
end
|
161
|
+
|
140
162
|
@http_defaults = HTTP_DEFAULTS.dup
|
141
163
|
options.each do |key, val|
|
142
164
|
if http_key = key[/^http_(.+)/, 1]
|
@@ -295,7 +317,7 @@ module Ably
|
|
295
317
|
# Connection used to make HTTP requests
|
296
318
|
#
|
297
319
|
# @param [Hash] options
|
298
|
-
# @option options [Boolean] :use_fallback when true, one of the fallback connections is used randomly, see {Ably::FALLBACK_HOSTS}
|
320
|
+
# @option options [Boolean] :use_fallback when true, one of the fallback connections is used randomly, see the default {Ably::FALLBACK_HOSTS}
|
299
321
|
#
|
300
322
|
# @return [Faraday::Connection]
|
301
323
|
#
|
@@ -309,14 +331,16 @@ module Ably
|
|
309
331
|
end
|
310
332
|
|
311
333
|
# Fallback connection used to make HTTP requests.
|
312
|
-
# Note, each request uses a random and then subsequent random {Ably::FALLBACK_HOSTS fallback
|
334
|
+
# Note, each request uses a random and then subsequent random {Ably::FALLBACK_HOSTS fallback hosts}
|
335
|
+
# are used (unless custom fallback hosts are provided with fallback_hosts)
|
313
336
|
#
|
314
337
|
# @return [Faraday::Connection]
|
315
338
|
#
|
316
339
|
# @api private
|
317
340
|
def fallback_connection
|
318
341
|
unless defined?(@fallback_connections) && @fallback_connections
|
319
|
-
@fallback_connections =
|
342
|
+
@fallback_connections = fallback_hosts.shuffle.map { |host| Faraday.new(endpoint_for_host(host).to_s, connection_options) }
|
343
|
+
@fallback_connections << Faraday.new(endpoint.to_s, connection_options) # Try the original host last if all fallbacks have been used
|
320
344
|
end
|
321
345
|
@fallback_index ||= 0
|
322
346
|
|
@@ -325,6 +349,16 @@ module Ably
|
|
325
349
|
end
|
326
350
|
end
|
327
351
|
|
352
|
+
# Library Ably version user agent
|
353
|
+
# @api private
|
354
|
+
def lib_version_id
|
355
|
+
@lib_version_id ||= [
|
356
|
+
'ruby',
|
357
|
+
Ably.lib_variant,
|
358
|
+
Ably::VERSION
|
359
|
+
].compact.join('-')
|
360
|
+
end
|
361
|
+
|
328
362
|
private
|
329
363
|
def request(method, path, params = {}, options = {})
|
330
364
|
options = options.clone
|
@@ -352,14 +386,13 @@ module Ably
|
|
352
386
|
unless options[:send_auth_header] == false
|
353
387
|
request.headers[:authorization] = auth.auth_header
|
354
388
|
end
|
355
|
-
request.headers['X-Ably-Version'] = Ably::PROTOCOL_VERSION
|
356
|
-
request.headers['X-Ably-Lib'] = Ably::LIB_VERSION_ID
|
357
389
|
end
|
358
390
|
|
359
391
|
rescue Faraday::TimeoutError, Faraday::ClientError, Ably::Exceptions::ServerError => error
|
360
392
|
time_passed = Time.now - requested_at
|
361
393
|
if can_fallback_to_alternate_ably_host? && retry_count < max_retry_count && time_passed <= max_retry_duration
|
362
394
|
retry_count += 1
|
395
|
+
logger.warn "Ably::Rest::Client - Retry #{retry_count} for #{method} #{path} #{params} as initial attempt failed: #{error}"
|
363
396
|
retry
|
364
397
|
end
|
365
398
|
|
@@ -410,9 +443,11 @@ module Ably
|
|
410
443
|
@connection_options ||= {
|
411
444
|
builder: middleware,
|
412
445
|
headers: {
|
413
|
-
content_type:
|
414
|
-
accept:
|
415
|
-
user_agent:
|
446
|
+
content_type: mime_type,
|
447
|
+
accept: mime_type,
|
448
|
+
user_agent: user_agent,
|
449
|
+
'X-Ably-Version' => Ably::PROTOCOL_VERSION,
|
450
|
+
'X-Ably-Lib' => lib_version_id
|
416
451
|
},
|
417
452
|
request: {
|
418
453
|
open_timeout: http_defaults.fetch(:open_timeout),
|
@@ -439,7 +474,7 @@ module Ably
|
|
439
474
|
end
|
440
475
|
|
441
476
|
def can_fallback_to_alternate_ably_host?
|
442
|
-
|
477
|
+
fallback_hosts && !fallback_hosts.empty?
|
443
478
|
end
|
444
479
|
|
445
480
|
def initialize_default_encoders
|
data/lib/ably/version.rb
CHANGED
@@ -1,5 +1,16 @@
|
|
1
1
|
module Ably
|
2
|
-
VERSION = '0.8.
|
2
|
+
VERSION = '0.8.14'
|
3
3
|
PROTOCOL_VERSION = '0.8'
|
4
|
-
|
4
|
+
|
5
|
+
# Allow a variant to be configured for all instances of this client library
|
6
|
+
# such as ruby-rest-[VERSION]
|
7
|
+
|
8
|
+
# @api private
|
9
|
+
def self.lib_variant=(variant)
|
10
|
+
@lib_variant = variant
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.lib_variant
|
14
|
+
@lib_variant
|
15
|
+
end
|
5
16
|
end
|
@@ -62,7 +62,7 @@ describe Ably::Realtime::Connection, 'failures', :event_machine do
|
|
62
62
|
log_level: :none,
|
63
63
|
disconnected_retry_timeout: retry_every_for_tests,
|
64
64
|
suspended_retry_timeout: retry_every_for_tests,
|
65
|
-
|
65
|
+
max_connection_state_ttl: max_time_in_state_for_tests
|
66
66
|
)
|
67
67
|
end
|
68
68
|
|
@@ -169,7 +169,7 @@ describe Ably::Realtime::Connection, 'failures', :event_machine do
|
|
169
169
|
|
170
170
|
context 'for the first time' do
|
171
171
|
let(:client_options) do
|
172
|
-
default_options.merge(suspended_retry_timeout: 2,
|
172
|
+
default_options.merge(suspended_retry_timeout: 2, max_connection_state_ttl: 0, log_level: :error)
|
173
173
|
end
|
174
174
|
|
175
175
|
it 'waits suspended_retry_timeout before attempting to reconnect' do
|
@@ -306,7 +306,7 @@ describe Ably::Realtime::Connection, 'failures', :event_machine do
|
|
306
306
|
log_level: :error,
|
307
307
|
disconnected_retry_timeout: 0.1,
|
308
308
|
suspended_retry_timeout: 0.1,
|
309
|
-
|
309
|
+
max_connection_state_ttl: 0.2,
|
310
310
|
realtime_host: 'non.existent.host'
|
311
311
|
)
|
312
312
|
end
|
@@ -370,7 +370,7 @@ describe Ably::Realtime::Connection, 'failures', :event_machine do
|
|
370
370
|
log_level: :none,
|
371
371
|
disconnected_retry_timeout: retry_every,
|
372
372
|
suspended_retry_timeout: retry_every,
|
373
|
-
|
373
|
+
max_connection_state_ttl: 60
|
374
374
|
)
|
375
375
|
end
|
376
376
|
|
@@ -416,7 +416,7 @@ describe Ably::Realtime::Connection, 'failures', :event_machine do
|
|
416
416
|
end
|
417
417
|
end
|
418
418
|
|
419
|
-
context 'when websocket transport is
|
419
|
+
context 'when websocket transport is abruptly disconnected' do
|
420
420
|
it 'reconnects automatically' do
|
421
421
|
fail_if_suspended_or_failed
|
422
422
|
|
@@ -431,6 +431,29 @@ describe Ably::Realtime::Connection, 'failures', :event_machine do
|
|
431
431
|
connection.transport.close_connection_after_writing
|
432
432
|
end
|
433
433
|
end
|
434
|
+
|
435
|
+
context 'hosts used' do
|
436
|
+
it 'reconnects with the default host' do
|
437
|
+
fail_if_suspended_or_failed
|
438
|
+
|
439
|
+
connection.once(:connected) do
|
440
|
+
connection.once(:disconnected) do
|
441
|
+
hosts = []
|
442
|
+
expect(connection).to receive(:create_transport).once.and_wrap_original do |original_method, *args, &block|
|
443
|
+
hosts << args[0]
|
444
|
+
original_method.call(*args, &block)
|
445
|
+
end
|
446
|
+
connection.once(:connected) do
|
447
|
+
host = "#{"#{environment}-" if environment && environment.to_s != 'production'}#{Ably::Realtime::Client::DOMAIN}"
|
448
|
+
expect(hosts.first).to eql(host)
|
449
|
+
expect(hosts.length).to eql(1)
|
450
|
+
stop_reactor
|
451
|
+
end
|
452
|
+
end
|
453
|
+
connection.transport.close_connection_after_writing
|
454
|
+
end
|
455
|
+
end
|
456
|
+
end
|
434
457
|
end
|
435
458
|
|
436
459
|
context 'after successfully reconnecting and resuming' do
|
@@ -599,6 +622,32 @@ describe Ably::Realtime::Connection, 'failures', :event_machine do
|
|
599
622
|
end
|
600
623
|
end
|
601
624
|
end
|
625
|
+
|
626
|
+
context 'as the DISCONNECTED window to resume has passed' do
|
627
|
+
let(:channel) { client.channel(random_str) }
|
628
|
+
|
629
|
+
def kill_connection_transport_and_prevent_valid_resume
|
630
|
+
connection.transport.close_connection_after_writing
|
631
|
+
end
|
632
|
+
|
633
|
+
it 'starts a new connection automatically and does not try and resume' do
|
634
|
+
connection.once(:connected) do
|
635
|
+
previous_connection_id = connection.id
|
636
|
+
previous_connection_key = connection.key
|
637
|
+
|
638
|
+
five_minutes_time = Time.now + 5 * 60
|
639
|
+
allow(Time).to receive(:now) { five_minutes_time }
|
640
|
+
|
641
|
+
connection.once(:connected) do
|
642
|
+
expect(connection.key).to_not eql(previous_connection_key)
|
643
|
+
expect(connection.id).to_not eql(previous_connection_id)
|
644
|
+
stop_reactor
|
645
|
+
end
|
646
|
+
|
647
|
+
kill_connection_transport_and_prevent_valid_resume
|
648
|
+
end
|
649
|
+
end
|
650
|
+
end
|
602
651
|
end
|
603
652
|
end
|
604
653
|
|
@@ -612,7 +661,7 @@ describe Ably::Realtime::Connection, 'failures', :event_machine do
|
|
612
661
|
log_level: :none,
|
613
662
|
disconnected_retry_timeout: retry_every_for_tests,
|
614
663
|
suspended_retry_timeout: retry_every_for_tests,
|
615
|
-
|
664
|
+
max_connection_state_ttl: max_time_in_state_for_tests
|
616
665
|
)
|
617
666
|
end
|
618
667
|
|
@@ -627,7 +676,7 @@ describe Ably::Realtime::Connection, 'failures', :event_machine do
|
|
627
676
|
let(:client_options) { timeout_options.merge(realtime_host: expected_host) }
|
628
677
|
|
629
678
|
it 'never uses a fallback host' do
|
630
|
-
expect(
|
679
|
+
expect(connection).to receive(:create_transport).exactly(retry_count_for_all_states).times do |host|
|
631
680
|
expect(host).to eql(expected_host)
|
632
681
|
raise EventMachine::ConnectionError
|
633
682
|
end
|
@@ -645,7 +694,7 @@ describe Ably::Realtime::Connection, 'failures', :event_machine do
|
|
645
694
|
let(:client_options) { timeout_options.merge(tls_port: custom_port) }
|
646
695
|
|
647
696
|
it 'never uses a fallback host' do
|
648
|
-
expect(
|
697
|
+
expect(connection).to receive(:create_transport).exactly(retry_count_for_all_states).times do |host, port|
|
649
698
|
expect(port).to eql(custom_port)
|
650
699
|
raise EventMachine::ConnectionError
|
651
700
|
end
|
@@ -663,8 +712,8 @@ describe Ably::Realtime::Connection, 'failures', :event_machine do
|
|
663
712
|
let(:expected_host) { "#{environment}-#{Ably::Realtime::Client::DOMAIN}" }
|
664
713
|
let(:client_options) { timeout_options.merge(environment: environment) }
|
665
714
|
|
666
|
-
it '
|
667
|
-
expect(
|
715
|
+
it 'does not use a fallback host by default' do
|
716
|
+
expect(connection).to receive(:create_transport).exactly(retry_count_for_all_states).times do |host|
|
668
717
|
expect(host).to eql(expected_host)
|
669
718
|
raise EventMachine::ConnectionError
|
670
719
|
end
|
@@ -675,6 +724,55 @@ describe Ably::Realtime::Connection, 'failures', :event_machine do
|
|
675
724
|
end
|
676
725
|
end
|
677
726
|
end
|
727
|
+
|
728
|
+
context ':fallback_hosts_use_default is true' do
|
729
|
+
let(:max_time_in_state_for_tests) { 4 }
|
730
|
+
let(:fallback_hosts_used) { Array.new }
|
731
|
+
let(:client_options) { timeout_options.merge(environment: environment, fallback_hosts_use_default: true) }
|
732
|
+
|
733
|
+
it 'uses a fallback host on every subsequent disconnected attempt until suspended (#RTN17b, #TO3k7)' do
|
734
|
+
request = 0
|
735
|
+
allow(connection).to receive(:create_transport) do |host|
|
736
|
+
if request == 0
|
737
|
+
expect(host).to eql(expected_host)
|
738
|
+
else
|
739
|
+
fallback_hosts_used << host
|
740
|
+
end
|
741
|
+
request += 1
|
742
|
+
raise EventMachine::ConnectionError
|
743
|
+
end
|
744
|
+
|
745
|
+
connection.once(:suspended) do
|
746
|
+
expect(fallback_hosts_used.uniq).to match_array(Ably::FALLBACK_HOSTS + [expected_host])
|
747
|
+
stop_reactor
|
748
|
+
end
|
749
|
+
end
|
750
|
+
end
|
751
|
+
|
752
|
+
context ':fallback_hosts array is provided' do
|
753
|
+
let(:max_time_in_state_for_tests) { 4 }
|
754
|
+
let(:fallback_hosts) { %w(a.foo.com b.foo.com) }
|
755
|
+
let(:fallback_hosts_used) { Array.new }
|
756
|
+
let(:client_options) { timeout_options.merge(environment: environment, fallback_hosts: fallback_hosts) }
|
757
|
+
|
758
|
+
it 'uses a fallback host on every subsequent disconnected attempt until suspended (#RTN17b, #TO3k6)' do
|
759
|
+
request = 0
|
760
|
+
allow(connection).to receive(:create_transport) do |host|
|
761
|
+
if request == 0
|
762
|
+
expect(host).to eql(expected_host)
|
763
|
+
else
|
764
|
+
fallback_hosts_used << host
|
765
|
+
end
|
766
|
+
request += 1
|
767
|
+
raise EventMachine::ConnectionError
|
768
|
+
end
|
769
|
+
|
770
|
+
connection.once(:suspended) do
|
771
|
+
expect(fallback_hosts_used.uniq).to match_array(fallback_hosts + [expected_host])
|
772
|
+
stop_reactor
|
773
|
+
end
|
774
|
+
end
|
775
|
+
end
|
678
776
|
end
|
679
777
|
|
680
778
|
context 'with production environment' do
|
@@ -694,7 +792,7 @@ describe Ably::Realtime::Connection, 'failures', :event_machine do
|
|
694
792
|
end
|
695
793
|
|
696
794
|
it 'never uses a fallback host' do
|
697
|
-
expect(
|
795
|
+
expect(connection).to receive(:create_transport).exactly(retry_count_for_all_states).times do |host|
|
698
796
|
expect(host).to eql(expected_host)
|
699
797
|
raise EventMachine::ConnectionError
|
700
798
|
end
|
@@ -713,43 +811,92 @@ describe Ably::Realtime::Connection, 'failures', :event_machine do
|
|
713
811
|
@suspended = 0
|
714
812
|
end
|
715
813
|
|
716
|
-
|
717
|
-
|
718
|
-
|
719
|
-
|
720
|
-
|
721
|
-
|
722
|
-
|
814
|
+
context 'and default options' do
|
815
|
+
let(:max_time_in_state_for_tests) { 2 } # allow time for 3 attempts, 2 configured fallbacks + primary host
|
816
|
+
|
817
|
+
it 'uses a fallback host + the original host once on every subsequent disconnected attempt until suspended' do
|
818
|
+
request = 0
|
819
|
+
expect(connection).to receive(:create_transport).exactly(retry_count_for_one_state).times do |host|
|
820
|
+
if request == 0
|
821
|
+
expect(host).to eql(expected_host)
|
822
|
+
else
|
823
|
+
fallback_hosts_used << host
|
824
|
+
end
|
825
|
+
request += 1
|
826
|
+
raise EventMachine::ConnectionError
|
827
|
+
end
|
828
|
+
|
829
|
+
connection.once(:suspended) do
|
830
|
+
fallback_hosts_used.pop # remove suspended attempt host
|
831
|
+
expect(fallback_hosts_used.uniq).to match_array(custom_hosts + [expected_host])
|
832
|
+
stop_reactor
|
723
833
|
end
|
724
|
-
request += 1
|
725
|
-
raise EventMachine::ConnectionError
|
726
834
|
end
|
727
835
|
|
728
|
-
|
729
|
-
|
730
|
-
expect(
|
731
|
-
|
836
|
+
it 'uses the primary host when suspended, and then every fallback host and the primary host again on every subsequent suspended attempt' do
|
837
|
+
request = 0
|
838
|
+
expect(connection).to receive(:create_transport).at_least(:once) do |host|
|
839
|
+
if request == 0 || request == expected_retry_attempts + 1
|
840
|
+
expect(host).to eql(expected_host)
|
841
|
+
else
|
842
|
+
expect(custom_hosts + [expected_host]).to include(host)
|
843
|
+
fallback_hosts_used << host if @suspended > 0
|
844
|
+
end
|
845
|
+
request += 1
|
846
|
+
raise EventMachine::ConnectionError
|
847
|
+
end
|
848
|
+
|
849
|
+
connection.on(:suspended) do
|
850
|
+
@suspended += 1
|
851
|
+
|
852
|
+
if @suspended > 4
|
853
|
+
expect(fallback_hosts_used.uniq).to match_array(custom_hosts + [expected_host])
|
854
|
+
stop_reactor
|
855
|
+
end
|
856
|
+
end
|
732
857
|
end
|
733
858
|
end
|
734
859
|
|
735
|
-
|
736
|
-
|
737
|
-
|
738
|
-
|
739
|
-
|
740
|
-
|
741
|
-
|
742
|
-
|
860
|
+
context ':fallback_hosts array is provided by an empty array' do
|
861
|
+
let(:max_time_in_state_for_tests) { 3 }
|
862
|
+
let(:fallback_hosts) { [] }
|
863
|
+
let(:hosts_used) { Array.new }
|
864
|
+
let(:client_options) { timeout_options.merge(environment: 'production', fallback_hosts: fallback_hosts) }
|
865
|
+
|
866
|
+
it 'uses a fallback host on every subsequent disconnected attempt until suspended (#RTN17b, #TO3k6)' do
|
867
|
+
allow(connection).to receive(:create_transport) do |host|
|
868
|
+
hosts_used << host
|
869
|
+
raise EventMachine::ConnectionError
|
870
|
+
end
|
871
|
+
|
872
|
+
connection.once(:suspended) do
|
873
|
+
expect(hosts_used.uniq.length).to eql(1)
|
874
|
+
expect(hosts_used.uniq.first).to eql(expected_host)
|
875
|
+
stop_reactor
|
743
876
|
end
|
744
|
-
request += 1
|
745
|
-
raise EventMachine::ConnectionError
|
746
877
|
end
|
878
|
+
end
|
879
|
+
|
880
|
+
context ':fallback_hosts array is provided' do
|
881
|
+
let(:max_time_in_state_for_tests) { 3 }
|
882
|
+
let(:fallback_hosts) { %w(a.foo.com b.foo.com) }
|
883
|
+
let(:fallback_hosts_used) { Array.new }
|
884
|
+
let(:client_options) { timeout_options.merge(environment: 'production', fallback_hosts: fallback_hosts) }
|
747
885
|
|
748
|
-
|
749
|
-
|
886
|
+
it 'uses a fallback host on every subsequent disconnected attempt until suspended (#RTN17b, #TO3k6)' do
|
887
|
+
request = 0
|
888
|
+
allow(connection).to receive(:create_transport) do |host|
|
889
|
+
if request == 0
|
890
|
+
expect(host).to eql(expected_host)
|
891
|
+
else
|
892
|
+
fallback_hosts_used << host
|
893
|
+
end
|
894
|
+
request += 1
|
895
|
+
raise EventMachine::ConnectionError
|
896
|
+
end
|
750
897
|
|
751
|
-
|
752
|
-
expect(fallback_hosts_used.uniq).to match_array(
|
898
|
+
connection.once(:suspended) do
|
899
|
+
expect(fallback_hosts_used.uniq).to match_array(fallback_hosts + [expected_host])
|
753
900
|
stop_reactor
|
754
901
|
end
|
755
902
|
end
|
@@ -833,6 +833,22 @@ describe Ably::Realtime::Connection, :event_machine do
|
|
833
833
|
end
|
834
834
|
end
|
835
835
|
end
|
836
|
+
|
837
|
+
context 'with a different connection_state_ttl' do
|
838
|
+
before do
|
839
|
+
old_defaults = Ably::Realtime::Connection::DEFAULTS
|
840
|
+
stub_const 'Ably::Realtime::Connection::DEFAULTS', old_defaults.merge(connection_state_ttl: 15)
|
841
|
+
end
|
842
|
+
|
843
|
+
it 'updates the private Connection#connection_state_ttl' do
|
844
|
+
expect(connection.connection_state_ttl).to eql(15)
|
845
|
+
|
846
|
+
connection.once(:connected) do
|
847
|
+
expect(connection.connection_state_ttl).to be > 15
|
848
|
+
stop_reactor
|
849
|
+
end
|
850
|
+
end
|
851
|
+
end
|
836
852
|
end
|
837
853
|
|
838
854
|
context 'recovery' do
|
@@ -848,14 +864,14 @@ describe Ably::Realtime::Connection, :event_machine do
|
|
848
864
|
log_level: :none,
|
849
865
|
disconnected_retry_timeout: 0.1,
|
850
866
|
suspended_retry_timeout: 0.1,
|
851
|
-
|
867
|
+
max_connection_state_ttl: 0.2,
|
852
868
|
realtime_request_timeout: 5
|
853
869
|
)
|
854
870
|
end
|
855
871
|
|
856
872
|
describe '#recovery_key' do
|
857
873
|
def self.available_states
|
858
|
-
[:connecting, :connected, :disconnected
|
874
|
+
[:connecting, :connected, :disconnected]
|
859
875
|
end
|
860
876
|
let(:available_states) { self.class.available_states }
|
861
877
|
let(:states) { Hash.new }
|
@@ -968,19 +984,25 @@ describe Ably::Realtime::Connection, :event_machine do
|
|
968
984
|
context 'when messages have been sent whilst the old connection is disconnected' do
|
969
985
|
describe 'the new connection' do
|
970
986
|
it 'recovers server-side queued messages' do
|
987
|
+
connection_id, recovery_key = nil, nil
|
988
|
+
|
971
989
|
channel.attach do
|
972
|
-
connection.
|
990
|
+
connection_id = client.connection.id
|
991
|
+
recovery_key = client.connection.recovery_key
|
992
|
+
connection.transport.__incoming_protocol_msgbus__
|
993
|
+
publishing_client_channel.publish('event', 'message') do
|
994
|
+
connection.transition_state_machine! :failed
|
995
|
+
end
|
973
996
|
end
|
974
997
|
|
975
998
|
connection.on(:failed) do
|
976
|
-
|
977
|
-
|
978
|
-
|
979
|
-
|
980
|
-
|
981
|
-
|
982
|
-
|
983
|
-
end
|
999
|
+
recover_client = auto_close Ably::Realtime::Client.new(default_options.merge(recover: recovery_key))
|
1000
|
+
recover_client_channel = recover_client.channel(channel_name)
|
1001
|
+
recover_client_channel.attach do
|
1002
|
+
expect(recover_client.connection.id).to eql(connection_id)
|
1003
|
+
recover_client_channel.subscribe('event') do |message|
|
1004
|
+
expect(message.data).to eql('message')
|
1005
|
+
stop_reactor
|
984
1006
|
end
|
985
1007
|
end
|
986
1008
|
end
|
@@ -1212,7 +1234,7 @@ describe Ably::Realtime::Connection, :event_machine do
|
|
1212
1234
|
log_level: :fatal,
|
1213
1235
|
disconnected_retry_timeout: 0.02,
|
1214
1236
|
suspended_retry_timeout: 60,
|
1215
|
-
|
1237
|
+
max_connection_state_ttl: 0.05
|
1216
1238
|
)
|
1217
1239
|
end
|
1218
1240
|
|
@@ -1375,6 +1397,27 @@ describe Ably::Realtime::Connection, :event_machine do
|
|
1375
1397
|
end
|
1376
1398
|
client
|
1377
1399
|
end
|
1400
|
+
|
1401
|
+
context 'with variant' do
|
1402
|
+
let(:variant) { 'foo ' }
|
1403
|
+
|
1404
|
+
before do
|
1405
|
+
Ably.lib_variant = variant
|
1406
|
+
end
|
1407
|
+
|
1408
|
+
after do
|
1409
|
+
Ably.lib_variant = nil
|
1410
|
+
end
|
1411
|
+
|
1412
|
+
it 'sends the lib version param lib with the variant' do
|
1413
|
+
expect(EventMachine).to receive(:connect) do |host, port, transport, object, url|
|
1414
|
+
uri = URI.parse(url)
|
1415
|
+
expect(CGI::parse(uri.query)['lib'][0]).to eql("ruby-#{variant}-#{Ably::VERSION}")
|
1416
|
+
stop_reactor
|
1417
|
+
end
|
1418
|
+
client
|
1419
|
+
end
|
1420
|
+
end
|
1378
1421
|
end
|
1379
1422
|
end
|
1380
1423
|
end
|
@@ -1,5 +1,6 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
require 'spec_helper'
|
3
|
+
require 'webrick'
|
3
4
|
|
4
5
|
describe Ably::Rest::Client do
|
5
6
|
vary_by_protocol do
|
@@ -214,7 +215,7 @@ describe Ably::Rest::Client do
|
|
214
215
|
|
215
216
|
context 'connection transport' do
|
216
217
|
context 'defaults' do
|
217
|
-
let(:client_options) { default_options.merge(key: api_key) }
|
218
|
+
let(:client_options) { default_options.merge(key: api_key, environment: 'production') }
|
218
219
|
|
219
220
|
context 'for default host' do
|
220
221
|
it "is configured to timeout connection opening in #{http_defaults.fetch(:open_timeout)} seconds" do
|
@@ -240,7 +241,7 @@ describe Ably::Rest::Client do
|
|
240
241
|
context 'with custom http_open_timeout and http_request_timeout options' do
|
241
242
|
let(:http_open_timeout) { 999 }
|
242
243
|
let(:http_request_timeout) { 666 }
|
243
|
-
let(:client_options) { default_options.merge(key: api_key, http_open_timeout: http_open_timeout, http_request_timeout: http_request_timeout) }
|
244
|
+
let(:client_options) { default_options.merge(key: api_key, http_open_timeout: http_open_timeout, http_request_timeout: http_request_timeout, environment: 'production') }
|
244
245
|
|
245
246
|
context 'for default host' do
|
246
247
|
it 'is configured to use custom open timeout' do
|
@@ -269,7 +270,7 @@ describe Ably::Rest::Client do
|
|
269
270
|
let(:publish_block) { proc { client.channel('test').publish('event', 'data') } }
|
270
271
|
|
271
272
|
context 'configured' do
|
272
|
-
let(:client_options) { default_options.merge(key: api_key) }
|
273
|
+
let(:client_options) { default_options.merge(key: api_key, environment: 'production') }
|
273
274
|
|
274
275
|
it 'should make connection attempts to A.ably-realtime.com, B.ably-realtime.com, C.ably-realtime.com, D.ably-realtime.com, E.ably-realtime.com' do
|
275
276
|
hosts = []
|
@@ -411,6 +412,279 @@ describe Ably::Rest::Client do
|
|
411
412
|
end
|
412
413
|
end
|
413
414
|
end
|
415
|
+
|
416
|
+
context 'when environment is production and server returns a 50x error' do
|
417
|
+
let(:custom_hosts) { %w(A.foo.com B.foo.com) }
|
418
|
+
let(:max_retry_count) { 2 }
|
419
|
+
let(:max_retry_duration) { 0.5 }
|
420
|
+
let(:fallback_block) { Proc.new { raise Faraday::SSLError.new('ssl error message') } }
|
421
|
+
let(:production_options) do
|
422
|
+
default_options.merge(
|
423
|
+
environment: nil,
|
424
|
+
key: api_key,
|
425
|
+
http_max_retry_duration: max_retry_duration,
|
426
|
+
http_max_retry_count: max_retry_count
|
427
|
+
)
|
428
|
+
end
|
429
|
+
|
430
|
+
let(:status) { 502 }
|
431
|
+
let(:fallback_block) do
|
432
|
+
Proc.new do
|
433
|
+
{
|
434
|
+
headers: { 'Content-Type' => 'text/html' },
|
435
|
+
status: status
|
436
|
+
}
|
437
|
+
end
|
438
|
+
end
|
439
|
+
let!(:default_host_request_stub) do
|
440
|
+
stub_request(:post, "https://#{api_key}@#{Ably::Rest::Client::DOMAIN}#{path}").to_return(&fallback_block)
|
441
|
+
end
|
442
|
+
|
443
|
+
context 'with custom fallback hosts provided' do
|
444
|
+
let!(:first_fallback_request_stub) do
|
445
|
+
stub_request(:post, "https://#{api_key}@#{custom_hosts[0]}#{path}").to_return(&fallback_block)
|
446
|
+
end
|
447
|
+
|
448
|
+
let!(:second_fallback_request_stub) do
|
449
|
+
stub_request(:post, "https://#{api_key}@#{custom_hosts[1]}#{path}").to_return(&fallback_block)
|
450
|
+
end
|
451
|
+
|
452
|
+
let(:client_options) {
|
453
|
+
production_options.merge(fallback_hosts: custom_hosts)
|
454
|
+
}
|
455
|
+
|
456
|
+
it 'attempts the fallback hosts as this is an authentication failure (#RSC15b, #TO3k6)' do
|
457
|
+
expect { publish_block.call }.to raise_error(Ably::Exceptions::ServerError)
|
458
|
+
expect(default_host_request_stub).to have_been_requested
|
459
|
+
expect(first_fallback_request_stub).to have_been_requested
|
460
|
+
expect(second_fallback_request_stub).to have_been_requested
|
461
|
+
end
|
462
|
+
end
|
463
|
+
|
464
|
+
context 'with an empty array of fallback hosts provided (#RSC15b, #TO3k6)' do
|
465
|
+
let(:client_options) {
|
466
|
+
production_options.merge(fallback_hosts: [])
|
467
|
+
}
|
468
|
+
|
469
|
+
it 'does not attempt the fallback hosts as this is an authentication failure' do
|
470
|
+
expect { publish_block.call }.to raise_error(Ably::Exceptions::ServerError)
|
471
|
+
expect(default_host_request_stub).to have_been_requested
|
472
|
+
end
|
473
|
+
end
|
474
|
+
|
475
|
+
context 'using a local web-server', webmock: false do
|
476
|
+
let(:primary_host) { 'local-rest.ably.io' }
|
477
|
+
let(:fallbacks) { ['local.ably.io', 'localhost'] }
|
478
|
+
let(:port) { rand(10000) + 2000 }
|
479
|
+
let(:channel_name) { 'foo' }
|
480
|
+
let(:request_timeout) { 3 }
|
481
|
+
|
482
|
+
after do
|
483
|
+
@web_server.shutdown
|
484
|
+
end
|
485
|
+
|
486
|
+
context 'and timing out the primary host' do
|
487
|
+
before do
|
488
|
+
@web_server = WEBrick::HTTPServer.new(:Port => port, :SSLEnable => false)
|
489
|
+
@web_server.mount_proc "/channels/#{channel_name}/publish" do |req, res|
|
490
|
+
if req.header["host"].first.include?(primary_host)
|
491
|
+
@primary_host_requested = true
|
492
|
+
sleep request_timeout + 0.5
|
493
|
+
else
|
494
|
+
@fallback_request_count ||= 0
|
495
|
+
@fallback_request_count += 1
|
496
|
+
if @fallback_request_count <= fail_fallback_request_count
|
497
|
+
sleep request_timeout + 0.5
|
498
|
+
else
|
499
|
+
res.status = 200
|
500
|
+
res['Content-Type'] = 'application/json'
|
501
|
+
res.body = '{}'
|
502
|
+
end
|
503
|
+
end
|
504
|
+
end
|
505
|
+
Thread.new do
|
506
|
+
@web_server.start
|
507
|
+
end
|
508
|
+
end
|
509
|
+
|
510
|
+
context 'with request timeout less than max_retry_duration' do
|
511
|
+
let(:client_options) do
|
512
|
+
default_options.merge(
|
513
|
+
rest_host: primary_host,
|
514
|
+
fallback_hosts: fallbacks,
|
515
|
+
token: 'fake.token',
|
516
|
+
port: port,
|
517
|
+
tls: false,
|
518
|
+
http_request_timeout: request_timeout,
|
519
|
+
max_retry_duration: request_timeout * 3
|
520
|
+
)
|
521
|
+
end
|
522
|
+
let(:fail_fallback_request_count) { 1 }
|
523
|
+
|
524
|
+
it 'tries one of the fallback hosts' do
|
525
|
+
client.channel(channel_name).publish('event', 'data')
|
526
|
+
expect(@primary_host_requested).to be_truthy
|
527
|
+
expect(@fallback_request_count).to eql(2)
|
528
|
+
end
|
529
|
+
end
|
530
|
+
|
531
|
+
context 'with request timeout less than max_retry_duration' do
|
532
|
+
let(:client_options) do
|
533
|
+
default_options.merge(
|
534
|
+
rest_host: primary_host,
|
535
|
+
fallback_hosts: fallbacks,
|
536
|
+
token: 'fake.token',
|
537
|
+
port: port,
|
538
|
+
tls: false,
|
539
|
+
http_request_timeout: request_timeout,
|
540
|
+
max_retry_duration: request_timeout / 2
|
541
|
+
)
|
542
|
+
end
|
543
|
+
let(:fail_fallback_request_count) { 0 }
|
544
|
+
|
545
|
+
it 'tries one of the fallback hosts' do
|
546
|
+
client.channel(channel_name).publish('event', 'data')
|
547
|
+
expect(@primary_host_requested).to be_truthy
|
548
|
+
expect(@fallback_request_count).to eql(1)
|
549
|
+
end
|
550
|
+
end
|
551
|
+
end
|
552
|
+
|
553
|
+
context 'and failing the primary host' do
|
554
|
+
before do
|
555
|
+
@web_server = WEBrick::HTTPServer.new(:Port => port, :SSLEnable => false)
|
556
|
+
@web_server.mount_proc "/channels/#{channel_name}/publish" do |req, res|
|
557
|
+
if req.header["host"].first.include?(primary_host)
|
558
|
+
@primary_host_requested = true
|
559
|
+
res.status = 500
|
560
|
+
else
|
561
|
+
@fallback_request_count ||= 0
|
562
|
+
@fallback_request_count += 1
|
563
|
+
if @fallback_request_count <= fail_fallback_request_count
|
564
|
+
res.status = 500
|
565
|
+
else
|
566
|
+
res.status = 200
|
567
|
+
res['Content-Type'] = 'application/json'
|
568
|
+
res.body = '{}'
|
569
|
+
end
|
570
|
+
end
|
571
|
+
end
|
572
|
+
Thread.new do
|
573
|
+
@web_server.start
|
574
|
+
end
|
575
|
+
end
|
576
|
+
|
577
|
+
let(:client_options) do
|
578
|
+
default_options.merge(
|
579
|
+
rest_host: primary_host,
|
580
|
+
fallback_hosts: fallbacks,
|
581
|
+
token: 'fake.token',
|
582
|
+
port: port,
|
583
|
+
tls: false
|
584
|
+
)
|
585
|
+
end
|
586
|
+
let(:fail_fallback_request_count) { 1 }
|
587
|
+
|
588
|
+
it 'tries one of the fallback hosts' do
|
589
|
+
client.channel(channel_name).publish('event', 'data')
|
590
|
+
expect(@primary_host_requested).to be_truthy
|
591
|
+
expect(@fallback_request_count).to eql(2)
|
592
|
+
end
|
593
|
+
end
|
594
|
+
end
|
595
|
+
end
|
596
|
+
|
597
|
+
context 'when environment is not production and server returns a 50x error' do
|
598
|
+
let(:custom_hosts) { %w(A.foo.com B.foo.com) }
|
599
|
+
let(:max_retry_count) { 2 }
|
600
|
+
let(:max_retry_duration) { 0.5 }
|
601
|
+
let(:fallback_block) { Proc.new { raise Faraday::SSLError.new('ssl error message') } }
|
602
|
+
let(:env) { 'custom-env' }
|
603
|
+
let(:production_options) do
|
604
|
+
default_options.merge(
|
605
|
+
environment: env,
|
606
|
+
key: api_key,
|
607
|
+
http_max_retry_duration: max_retry_duration,
|
608
|
+
http_max_retry_count: max_retry_count
|
609
|
+
)
|
610
|
+
end
|
611
|
+
|
612
|
+
let(:status) { 502 }
|
613
|
+
let(:fallback_block) do
|
614
|
+
Proc.new do
|
615
|
+
{
|
616
|
+
headers: { 'Content-Type' => 'text/html' },
|
617
|
+
status: status
|
618
|
+
}
|
619
|
+
end
|
620
|
+
end
|
621
|
+
let!(:default_host_request_stub) do
|
622
|
+
stub_request(:post, "https://#{api_key}@#{env}-#{Ably::Rest::Client::DOMAIN}#{path}").to_return(&fallback_block)
|
623
|
+
end
|
624
|
+
|
625
|
+
context 'with custom fallback hosts provided (#RSC15b, #TO3k6)' do
|
626
|
+
let!(:first_fallback_request_stub) do
|
627
|
+
stub_request(:post, "https://#{api_key}@#{custom_hosts[0]}#{path}").to_return(&fallback_block)
|
628
|
+
end
|
629
|
+
|
630
|
+
let!(:second_fallback_request_stub) do
|
631
|
+
stub_request(:post, "https://#{api_key}@#{custom_hosts[1]}#{path}").to_return(&fallback_block)
|
632
|
+
end
|
633
|
+
|
634
|
+
let(:client_options) {
|
635
|
+
production_options.merge(fallback_hosts: custom_hosts)
|
636
|
+
}
|
637
|
+
|
638
|
+
it 'attempts the fallback hosts as this is an authentication failure' do
|
639
|
+
expect { publish_block.call }.to raise_error(Ably::Exceptions::ServerError)
|
640
|
+
expect(default_host_request_stub).to have_been_requested
|
641
|
+
expect(first_fallback_request_stub).to have_been_requested
|
642
|
+
expect(second_fallback_request_stub).to have_been_requested
|
643
|
+
end
|
644
|
+
end
|
645
|
+
|
646
|
+
context 'with an empty array of fallback hosts provided (#RSC15b, #TO3k6)' do
|
647
|
+
let(:client_options) {
|
648
|
+
production_options.merge(fallback_hosts: [])
|
649
|
+
}
|
650
|
+
|
651
|
+
it 'does not attempt the fallback hosts as this is an authentication failure' do
|
652
|
+
expect { publish_block.call }.to raise_error(Ably::Exceptions::ServerError)
|
653
|
+
expect(default_host_request_stub).to have_been_requested
|
654
|
+
end
|
655
|
+
end
|
656
|
+
|
657
|
+
context 'with fallback_hosts_use_default: true (#RSC15b, #TO3k7)' do
|
658
|
+
let(:custom_hosts) { Ably::FALLBACK_HOSTS[0...2] }
|
659
|
+
|
660
|
+
before do
|
661
|
+
stub_const 'Ably::FALLBACK_HOSTS', custom_hosts
|
662
|
+
end
|
663
|
+
|
664
|
+
let(:client_options) {
|
665
|
+
production_options.merge(fallback_hosts_use_default: true)
|
666
|
+
}
|
667
|
+
|
668
|
+
let!(:first_fallback_request_stub) do
|
669
|
+
stub_request(:post, "https://#{api_key}@#{Ably::FALLBACK_HOSTS[0]}#{path}").to_return(&fallback_block)
|
670
|
+
end
|
671
|
+
|
672
|
+
let!(:second_fallback_request_stub) do
|
673
|
+
stub_request(:post, "https://#{api_key}@#{Ably::FALLBACK_HOSTS[1]}#{path}").to_return(&fallback_block)
|
674
|
+
end
|
675
|
+
|
676
|
+
let(:client_options) {
|
677
|
+
production_options.merge(fallback_hosts: custom_hosts)
|
678
|
+
}
|
679
|
+
|
680
|
+
it 'attempts the default fallback hosts as this is an authentication failure' do
|
681
|
+
expect { publish_block.call }.to raise_error(Ably::Exceptions::ServerError)
|
682
|
+
expect(default_host_request_stub).to have_been_requested
|
683
|
+
expect(first_fallback_request_stub).to have_been_requested
|
684
|
+
expect(second_fallback_request_stub).to have_been_requested
|
685
|
+
end
|
686
|
+
end
|
687
|
+
end
|
414
688
|
end
|
415
689
|
|
416
690
|
context 'with a custom host' do
|
@@ -547,19 +821,38 @@ describe Ably::Rest::Client do
|
|
547
821
|
end
|
548
822
|
|
549
823
|
context 'version headers', :webmock do
|
550
|
-
|
551
|
-
|
552
|
-
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
}).
|
557
|
-
to_return(status: 201, body: '{}', headers: { 'Content-Type' => 'application/json' })
|
558
|
-
end
|
824
|
+
[nil, 'foo'].each do |variant|
|
825
|
+
context "with variant #{variant ? variant : 'none'}" do
|
826
|
+
if variant
|
827
|
+
before do
|
828
|
+
Ably.lib_variant = variant
|
829
|
+
end
|
559
830
|
|
560
|
-
|
561
|
-
|
562
|
-
|
831
|
+
after do
|
832
|
+
Ably.lib_variant = nil
|
833
|
+
end
|
834
|
+
end
|
835
|
+
|
836
|
+
let(:client_options) { default_options.merge(key: api_key) }
|
837
|
+
let!(:publish_message_stub) do
|
838
|
+
lib = ['ruby']
|
839
|
+
lib << variant if variant
|
840
|
+
lib << Ably::VERSION
|
841
|
+
|
842
|
+
|
843
|
+
stub_request(:post, "#{client.endpoint.to_s.gsub('://', "://#{api_key}@")}/channels/foo/publish").
|
844
|
+
with(headers: {
|
845
|
+
'X-Ably-Version' => Ably::PROTOCOL_VERSION,
|
846
|
+
'X-Ably-Lib' => lib.join('-')
|
847
|
+
}).
|
848
|
+
to_return(status: 201, body: '{}', headers: { 'Content-Type' => 'application/json' })
|
849
|
+
end
|
850
|
+
|
851
|
+
it 'sends a protocol version and lib version header' do
|
852
|
+
client.channels.get('foo').publish("event")
|
853
|
+
expect(publish_message_stub).to have_been_requested
|
854
|
+
end
|
855
|
+
end
|
563
856
|
end
|
564
857
|
end
|
565
858
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ably
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.8.
|
4
|
+
version: 0.8.14
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Lewis Marshall
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2016-09-
|
12
|
+
date: 2016-09-30 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: eventmachine
|