Skip to content

Commit a7938e9

Browse files
committed
Use endpoint as default connection option (ADR-119)
This implements ADR-119[1], which specifies the client connection options to update requests to the endpoints implemented as part of ADR-042[2]. The endpoint may be one of the following: * a routing policy name (such as `main`) * a nonprod routing policy name (such as `nonprod:sandbox`) * a FQDN such as `foo.example.com` The endpoint option is not valid with any of environment, restHost or realtimeHost, but we still intend to support the legacy options. If the client has been configured to use any of these legacy options, then they should continue to work in the same way, using the same primary and fallback hostnames. If the client has not been explicitly configured, then the hostnames will change to the new `ably.net` domain when the package is upgraded. [1] https://ably.atlassian.net/wiki/spaces/ENG/pages/3428810778/ADR-119+ClientOptions+for+new+DNS+structure [2] https://ably.atlassian.net/wiki/spaces/ENG/pages/1791754276/ADR-042+DNS+Restructure
1 parent 181e0b1 commit a7938e9

22 files changed

+259
-152
lines changed

lib/ably/modules/ably.rb

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,26 @@
44
#
55
# @see file:README.md README
66
module Ably
7-
# Fallback hosts to use when a connection to rest/realtime.ably.io is not possible due to
7+
# Fallback hosts to use when a connection to main.realtime.ably.net is not possible due to
88
# network failures either at the client, between the client and Ably, within an Ably data center, or at the IO domain registrar
99
# see https://ably.com/docs/client-lib-development-guide/features/#RSC15a
1010
#
11-
FALLBACK_DOMAIN = 'ably-realtime.com'.freeze
11+
PROD_FALLBACK_DOMAIN = 'ably-realtime.com'.freeze
12+
NONPROD_FALLBACK_DOMAIN = 'ably-realtime-nonprod.com'.freeze
13+
1214
FALLBACK_IDS = %w(a b c d e).freeze
1315

14-
# Default production fallbacks a.ably-realtime.com ... e.ably-realtime.com
15-
FALLBACK_HOSTS = FALLBACK_IDS.map { |host| "#{host}.#{FALLBACK_DOMAIN}".freeze }.freeze
16+
# Default production fallbacks main.a.fallback.ably-realtime.com ... main.e.fallback.ably-realtime.com
17+
FALLBACK_HOSTS = FALLBACK_IDS.map { |host| "main.#{host}.fallback.#{PROD_FALLBACK_DOMAIN}".freeze }.freeze
18+
19+
# Prod fallback suffixes a.fallback.ably-realtime.com ... e.fallback.ably-realtime.com
20+
PROD_FALLBACKS_SUFFIXES = FALLBACK_IDS.map do |host|
21+
"#{host}.fallback.#{PROD_FALLBACK_DOMAIN}".freeze
22+
end.freeze
1623

17-
# Custom environment default fallbacks {ENV}-a-fallback.ably-realtime.com ... {ENV}-a-fallback.ably-realtime.com
18-
CUSTOM_ENVIRONMENT_FALLBACKS_SUFFIXES = FALLBACK_IDS.map do |host|
19-
"-#{host}-fallback.#{FALLBACK_DOMAIN}".freeze
24+
# Nonprod fallback suffixes a.fallback.ably-realtime-nonprod.com ... e.fallback.ably-realtime-nonprod.com
25+
NONPROD_FALLBACKS_SUFFIXES = FALLBACK_IDS.map do |host|
26+
"#{host}.fallback.#{NONPROD_FALLBACK_DOMAIN}".freeze
2027
end.freeze
2128

2229
INTERNET_CHECK = {

lib/ably/realtime/client.rb

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,6 @@ class Client
1414
extend Forwardable
1515
using Ably::Util::AblyExtensions
1616

17-
DOMAIN = 'realtime.ably.io'
18-
1917
# A {Aby::Realtime::Channels} object.
2018
#
2119
# @spec RTC3, RTS1
@@ -73,7 +71,7 @@ class Client
7371
def_delegators :auth, :client_id, :auth_options
7472
def_delegators :@rest_client, :encoders
7573
def_delegators :@rest_client, :use_tls?, :protocol, :protocol_binary?
76-
def_delegators :@rest_client, :environment, :custom_host, :custom_port, :custom_tls_port
74+
def_delegators :@rest_client, :endpoint, :environment, :custom_host, :custom_port, :custom_tls_port
7775
def_delegators :@rest_client, :log_level
7876
def_delegators :@rest_client, :options
7977

@@ -289,10 +287,34 @@ def publish(channel_name, name, data = nil, attributes = {}, &success_block)
289287
end
290288
end
291289

292-
# @!attribute [r] endpoint
290+
# @!attribute [r] hostname
291+
# @return [String] The primary hostname to connect to Ably
292+
def hostname
293+
if endpoint.include?('.') || endpoint.include?('::') || endpoint == 'localhost'
294+
return endpoint
295+
end
296+
297+
if endpoint.start_with?('nonprod:')
298+
"#{endpoint.gsub('nonprod:', '')}.realtime.#{root_domain}"
299+
else
300+
"#{endpoint}.realtime.#{root_domain}"
301+
end
302+
end
303+
304+
# @!attribute [r] root_domain
305+
# @return [String] The root domain used in the hostname
306+
def root_domain
307+
if endpoint.start_with?('nonprod:')
308+
'ably-nonprod.net'
309+
else
310+
'ably.net'
311+
end
312+
end
313+
314+
# @!attribute [r] uri
293315
# @return [URI::Generic] Default Ably Realtime endpoint used for all requests
294-
def endpoint
295-
endpoint_for_host(custom_realtime_host || [environment, DOMAIN].compact.join('-'))
316+
def uri
317+
uri_for_host(custom_realtime_host || hostname)
296318
end
297319

298320
# (see Ably::Rest::Client#register_encoder)
@@ -322,8 +344,8 @@ def disable_automatic_connection_recovery
322344
# @api private
323345
def fallback_endpoint
324346
unless defined?(@fallback_endpoints) && @fallback_endpoints
325-
@fallback_endpoints = fallback_hosts.shuffle.map { |fallback_host| endpoint_for_host(fallback_host) }
326-
@fallback_endpoints << endpoint # Try the original host last if all fallbacks have been used
347+
@fallback_endpoints = fallback_hosts.shuffle.map { |fallback_host| uri_for_host(fallback_host) }
348+
@fallback_endpoints << uri # Try the original host last if all fallbacks have been used
327349
end
328350

329351
fallback_endpoint_index = connection.manager.retry_count_for_state(:disconnected) + connection.manager.retry_count_for_state(:suspended) - 1
@@ -341,7 +363,8 @@ def device
341363
end
342364

343365
private
344-
def endpoint_for_host(host)
366+
367+
def uri_for_host(host)
345368
port = if use_tls?
346369
custom_tls_port
347370
else

lib/ably/realtime/connection.rb

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ def initialize(client, options)
173173
@state = STATE(state_machine.current_state)
174174
@manager = ConnectionManager.new(self)
175175

176-
@current_host = client.endpoint.host
176+
@current_host = client.uri.host
177177

178178
reset_client_msg_serial
179179
end
@@ -396,12 +396,12 @@ def determine_host
396396
@current_host = if internet_is_up_result
397397
client.fallback_endpoint.host
398398
else
399-
client.endpoint.host
399+
client.uri.host
400400
end
401401
yield current_host
402402
end
403403
else
404-
@current_host = client.endpoint.host
404+
@current_host = client.uri.host
405405
yield current_host
406406
end
407407
end
@@ -496,8 +496,8 @@ def create_websocket_transport
496496
end
497497
end
498498

499-
url = URI(client.endpoint).tap do |endpoint|
500-
endpoint.query = URI.encode_www_form(url_params)
499+
url = URI(client.uri).tap do |uri|
500+
uri.query = URI.encode_www_form(url_params)
501501
end
502502

503503
determine_host do |host|

lib/ably/rest/client.rb

Lines changed: 46 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,6 @@ class Client
1818
extend Forwardable
1919
using Ably::Util::AblyExtensions
2020

21-
# Default Ably domain for REST
22-
DOMAIN = 'rest.ably.io'
23-
2421
MAX_MESSAGE_SIZE = 65536 # See spec TO3l8
2522
MAX_FRAME_SIZE = 524288 # See spec TO3l8
2623

@@ -43,7 +40,11 @@ class Client
4340

4441
def_delegators :auth, :client_id, :auth_options
4542

46-
# Custom environment to use such as 'sandbox' when testing the client library against an alternate Ably environment
43+
# The hostname used to connect to Ably
44+
# @return [String]
45+
attr_reader :endpoint
46+
47+
# Custom environment to use such as 'sandbox' when testing the client library against an alternate Ably environment (deprecated)
4748
# @return [String]
4849
attr_reader :environment
4950

@@ -135,7 +136,8 @@ class Client
135136
# @option options [String] :token Token string or {Models::TokenDetails} used to authenticate requests
136137
# @option options [String] :token_details {Models::TokenDetails} used to authenticate requests
137138
# @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 [String] :endpoint Specify a routing policy or fully-qualified domain name to connect to Ably
140+
# @option options [String] :environment Specify 'sandbox' when testing the client library against an alternate Ably environment (deprecated)
139141
# @option options [Symbol] :protocol (:msgpack) Protocol used to communicate with Ably, :json and :msgpack currently supported
140142
# @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
141143
# @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
@@ -188,8 +190,6 @@ def initialize(options)
188190
@agent = options.delete(:agent) || Ably::AGENT
189191
@realtime_client = options.delete(:realtime_client)
190192
@tls = options.delete_with_default(:tls, true)
191-
@environment = options.delete(:environment) # nil is production
192-
@environment = nil if [:production, 'production'].include?(@environment)
193193
@protocol = options.delete(:protocol) || :msgpack
194194
@debug_http = options.delete(:debug_http)
195195
@log_level = options.delete(:log_level) || ::Logger::WARN
@@ -203,18 +203,25 @@ def initialize(options)
203203
@max_frame_size = options.delete(:max_frame_size) || MAX_FRAME_SIZE
204204
@idempotent_rest_publishing = options.delete_with_default(:idempotent_rest_publishing, true)
205205

206+
@environment = options.delete(:environment) # nil is production
207+
@environment = nil if [:production, 'production'].include?(@environment)
208+
@endpoint = environment || options.delete_with_default(:endpoint, 'main')
209+
206210
if options[:fallback_hosts_use_default] && options[:fallback_hosts]
207211
raise ArgumentError, "fallback_hosts_use_default cannot be set to try when fallback_hosts is also provided"
208212
end
213+
209214
@fallback_hosts = case
210215
when options.delete(:fallback_hosts_use_default)
211216
Ably::FALLBACK_HOSTS
212217
when options_fallback_hosts = options.delete(:fallback_hosts)
213218
options_fallback_hosts
214219
when custom_host || options[:realtime_host] || custom_port || custom_tls_port
215220
[]
216-
when environment
217-
CUSTOM_ENVIRONMENT_FALLBACKS_SUFFIXES.map { |host| "#{environment}#{host}" }
221+
when endpoint.start_with?('nonprod:')
222+
NONPROD_FALLBACKS_SUFFIXES.map { |host| "#{endpoint.gsub('nonprod:', '')}.#{host}" }
223+
when endpoint != 'main'
224+
PROD_FALLBACKS_SUFFIXES.map { |host| "#{endpoint}.#{host}" }
218225
else
219226
Ably::FALLBACK_HOSTS
220227
end
@@ -426,10 +433,34 @@ def push
426433
@push ||= Push.new(self)
427434
end
428435

429-
# @!attribute [r] endpoint
436+
# @!attribute [r] hostname
437+
# @return [String] The primary hostname to connect to Ably
438+
def hostname
439+
if endpoint.include?('.') || endpoint.include?('::') || endpoint == 'localhost'
440+
return endpoint
441+
end
442+
443+
if endpoint.start_with?('nonprod:')
444+
"#{endpoint.gsub('nonprod:', '')}.realtime.#{root_domain}"
445+
else
446+
"#{endpoint}.realtime.#{root_domain}"
447+
end
448+
end
449+
450+
# @!attribute [r] root_domain
451+
# @return [String] The root domain used in the hostname
452+
def root_domain
453+
if endpoint.start_with?('nonprod:')
454+
'ably-nonprod.net'
455+
else
456+
'ably.net'
457+
end
458+
end
459+
460+
# @!attribute [r] uri
430461
# @return [URI::Generic] Default Ably REST endpoint used for all requests
431-
def endpoint
432-
endpoint_for_host(custom_host || [@environment, DOMAIN].compact.join('-'))
462+
def uri
463+
uri_for_host(custom_host || hostname)
433464
end
434465

435466
# @!attribute [r] logger
@@ -480,7 +511,7 @@ def connection(options = {})
480511
if options[:use_fallback]
481512
fallback_connection
482513
else
483-
@connection ||= Faraday.new(endpoint.to_s, connection_options)
514+
@connection ||= Faraday.new(uri.to_s, connection_options)
484515
end
485516
end
486517

@@ -493,7 +524,7 @@ def connection(options = {})
493524
# @api private
494525
def fallback_connection
495526
unless defined?(@fallback_connections) && @fallback_connections
496-
@fallback_connections = fallback_hosts.shuffle.map { |host| Faraday.new(endpoint_for_host(host).to_s, connection_options) }
527+
@fallback_connections = fallback_hosts.shuffle.map { |host| Faraday.new(uri_for_host(host).to_s, connection_options) }
497528
end
498529
@fallback_index ||= 0
499530

@@ -653,7 +684,7 @@ def reauthorize_on_authorization_failure
653684
end
654685
end
655686

656-
def endpoint_for_host(host)
687+
def uri_for_host(host)
657688
port = if use_tls?
658689
custom_tls_port
659690
else

spec/acceptance/realtime/client_spec.rb

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,7 @@
234234
context '#request (#RSC19*)' do
235235
let(:client_options) { default_options.merge(key: api_key) }
236236
let(:device_id) { random_str }
237-
let(:endpoint) { subject.rest_client.endpoint }
237+
let(:uri) { subject.rest_client.uri }
238238

239239
context 'get' do
240240
it 'returns an HttpPaginatedResponse object' do
@@ -287,7 +287,7 @@
287287

288288
context 'post', :webmock do
289289
before do
290-
stub_request(:delete, "#{endpoint}/push/deviceRegistrations/#{device_id}/resetUpdateToken").
290+
stub_request(:delete, "#{uri}/push/deviceRegistrations/#{device_id}/resetUpdateToken").
291291
to_return(status: 200, body: '{}', headers: { 'Content-Type' => 'application/json' })
292292
end
293293

@@ -301,7 +301,7 @@
301301

302302
context 'delete', :webmock do
303303
before do
304-
stub_request(:delete, "#{endpoint}/push/channelSubscriptions?deviceId=#{device_id}").
304+
stub_request(:delete, "#{uri}/push/channelSubscriptions?deviceId=#{device_id}").
305305
to_return(status: 200, body: '{}', headers: { 'Content-Type' => 'application/json' })
306306
end
307307

@@ -317,7 +317,7 @@
317317
let(:body_params) { { 'metadata' => { 'key' => 'value' } } }
318318

319319
before do
320-
stub_request(:patch, "#{endpoint}/push/deviceRegistrations/#{device_id}")
320+
stub_request(:patch, "#{uri}/push/deviceRegistrations/#{device_id}")
321321
.with(body: serialize_body(body_params, protocol))
322322
.to_return(status: 200, body: '{}', headers: { 'Content-Type' => 'application/json' })
323323
end
@@ -341,7 +341,7 @@
341341
end
342342

343343
before do
344-
stub_request(:put, "#{endpoint}/push/deviceRegistrations/#{device_id}")
344+
stub_request(:put, "#{uri}/push/deviceRegistrations/#{device_id}")
345345
.with(body: serialize_body(body_params, protocol))
346346
.to_return(status: 200, body: '{}', headers: { 'Content-Type' => 'application/json' })
347347
end

spec/acceptance/realtime/connection_failures_spec.rb

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -930,7 +930,7 @@ def send_disconnect_message
930930
original_method.call(*args, &block)
931931
end
932932
connection.once(:connected) do
933-
host = "#{"#{environment}-" if environment && environment.to_s != 'production'}#{Ably::Realtime::Client::DOMAIN}"
933+
host = client.hostname
934934
expect(hosts.first).to eql(host)
935935
expect(hosts.length).to eql(1)
936936
stop_reactor
@@ -1469,13 +1469,13 @@ def kill_connection_transport_and_prevent_valid_resume
14691469
end
14701470

14711471
context 'with non-production environment' do
1472-
let(:environment) { 'sandbox' }
1473-
let(:expected_host) { "#{environment}-#{Ably::Realtime::Client::DOMAIN}" }
1472+
let(:environment) { 'nonprod:sandbox' }
1473+
let(:expected_host) { "sandbox.realtime.ably-nonprod.net" }
14741474
let(:client_options) { timeout_options.merge(environment: environment) }
14751475

14761476
context ':fallback_hosts_use_default is unset' do
14771477
let(:max_time_in_state_for_tests) { 8 }
1478-
let(:expected_hosts) { Ably::CUSTOM_ENVIRONMENT_FALLBACKS_SUFFIXES.map { |suffix| "#{environment}#{suffix}" } + [expected_host] }
1478+
let(:expected_hosts) { Ably::NONPROD_FALLBACKS_SUFFIXES.map { |suffix| "#{environment.gsub("nonprod:", "")}.#{suffix}" } + [expected_host] }
14791479
let(:fallback_hosts_used) { Array.new }
14801480

14811481
it 'uses fallback hosts by default' do
@@ -1568,7 +1568,7 @@ def kill_connection_transport_and_prevent_valid_resume
15681568
stub_const 'Ably::FALLBACK_HOSTS', custom_hosts
15691569
end
15701570

1571-
let(:expected_host) { Ably::Realtime::Client::DOMAIN }
1571+
let(:expected_host) { 'main.realtime.ably.net' }
15721572
let(:client_options) { timeout_options.merge(environment: nil) }
15731573

15741574
let(:fallback_hosts_used) { Array.new }

spec/acceptance/realtime/connection_spec.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929

3030
context 'current_host' do
3131
it 'is available immediately after the client is instanced' do
32-
expect(connection.current_host.to_s).to match(/\.ably\.io$/)
32+
expect(connection.current_host.to_s).to match(/\.realtime\.ably[-nonprod]*\.net$/)
3333
stop_reactor
3434
end
3535
end

spec/acceptance/realtime/message_spec.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -775,7 +775,7 @@ def publish_and_check_extras(extras)
775775
EventMachine.add_timer(0.0001) do
776776
connection.transition_state_machine :suspended
777777
stub_const 'Ably::FALLBACK_HOSTS', []
778-
allow(client).to receive(:endpoint).and_return(URI::Generic.build(scheme: 'wss', host: 'does.not.exist.com'))
778+
allow(client).to receive(:uri).and_return(URI::Generic.build(scheme: 'wss', host: 'does.not.exist.com'))
779779
end
780780
end
781781
end

spec/acceptance/realtime/push_admin_spec.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@
102102
end
103103

104104
let!(:publish_stub) do
105-
stub_request(:post, "#{client.rest_client.endpoint}/push/publish").
105+
stub_request(:post, "#{client.rest_client.uri}/push/publish").
106106
with do |request|
107107
expect(deserialize_body(request.body, protocol)['recipient']['camelCase']['secondLevelCamelCase']).to eql('val')
108108
expect(deserialize_body(request.body, protocol)['recipient']).to_not have_key('camel_case')
@@ -135,7 +135,7 @@
135135
'transportType' => 'ablyChannel',
136136
'channel' => channel,
137137
'ablyKey' => api_key,
138-
'ablyUrl' => client.rest_client.endpoint.to_s
138+
'ablyUrl' => client.rest_client.uri.to_s
139139
}
140140
end
141141
let(:notification_payload) do

0 commit comments

Comments
 (0)