Skip to content

Commit cc7ab95

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 35b0358 commit cc7ab95

File tree

17 files changed

+153
-115
lines changed

17 files changed

+153
-115
lines changed

lib/ably/modules/ably.rb

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,12 @@ module Ably
1111
FALLBACK_DOMAIN = 'ably-realtime.com'.freeze
1212
FALLBACK_IDS = %w(a b c d e).freeze
1313

14-
# Default production fallbacks a.ably-realtime.com ... e.ably-realtime.com
15-
FALLBACK_HOSTS = FALLBACK_IDS.map { |host| "#{host}.#{FALLBACK_DOMAIN}".freeze }.freeze
14+
# Default production fallbacks main.a.fallback.ably-realtime.com ... main.e.fallback.ably-realtime.com
15+
FALLBACK_HOSTS = FALLBACK_IDS.map { |host| "main.#{host}.fallback.#{FALLBACK_DOMAIN}".freeze }.freeze
1616

17-
# Custom environment default fallbacks {ENV}-a-fallback.ably-realtime.com ... {ENV}-a-fallback.ably-realtime.com
17+
# Custom environment default fallbacks {ENV}.a.fallback.ably-realtime.com ... {ENV}.e.fallback.ably-realtime.com
1818
CUSTOM_ENVIRONMENT_FALLBACKS_SUFFIXES = FALLBACK_IDS.map do |host|
19-
"-#{host}-fallback.#{FALLBACK_DOMAIN}".freeze
19+
".#{host}.fallback.#{FALLBACK_DOMAIN}".freeze
2020
end.freeze
2121

2222
INTERNET_CHECK = {

lib/ably/realtime/client.rb

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ class Client
7373
def_delegators :auth, :client_id, :auth_options
7474
def_delegators :@rest_client, :encoders
7575
def_delegators :@rest_client, :use_tls?, :protocol, :protocol_binary?
76-
def_delegators :@rest_client, :environment, :custom_host, :custom_port, :custom_tls_port
76+
def_delegators :@rest_client, :endpoint, :custom_host, :custom_port, :custom_tls_port
7777
def_delegators :@rest_client, :log_level
7878
def_delegators :@rest_client, :options
7979

@@ -289,10 +289,24 @@ def publish(channel_name, name, data = nil, attributes = {}, &success_block)
289289
end
290290
end
291291

292-
# @!attribute [r] endpoint
292+
# @!attribute [r] hostname
293+
# @return [String] The primary hostname to connect to Ably
294+
def hostname
295+
if endpoint.include?('.') || endpoint.include?('::') || endpoint == 'localhost'
296+
return endpoint
297+
end
298+
299+
if endpoint.start_with?('nonprod:')
300+
"#{endpoint.gsub('nonprod:', '')}.realtime.#{root_domain}"
301+
else
302+
"#{endpoint}.realtime.#{root_domain}"
303+
end
304+
end
305+
306+
# @!attribute [r] uri
293307
# @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('-'))
308+
def uri
309+
uri_for_host(custom_realtime_host || hostname)
296310
end
297311

298312
# (see Ably::Rest::Client#register_encoder)
@@ -341,7 +355,8 @@ def device
341355
end
342356

343357
private
344-
def endpoint_for_host(host)
358+
359+
def uri_for_host(host)
345360
port = if use_tls?
346361
custom_tls_port
347362
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: 43 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,9 @@ class Client
4343

4444
def_delegators :auth, :client_id, :auth_options
4545

46-
# Custom environment to use such as 'sandbox' when testing the client library against an alternate Ably environment
46+
# The hostname used to connect to Ably
4747
# @return [String]
48-
attr_reader :environment
48+
attr_reader :endpoint
4949

5050
# The protocol configured for this client, either binary `:msgpack` or text based `:json`
5151
# @return [Symbol]
@@ -135,7 +135,8 @@ class Client
135135
# @option options [String] :token Token string or {Models::TokenDetails} used to authenticate requests
136136
# @option options [String] :token_details {Models::TokenDetails} used to authenticate requests
137137
# @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
138+
# @option options [String] :endpoint Specify a routing policy or fully-qualified domain name to connect to Ably
139+
# @option options [String] :environment Specify 'sandbox' when testing the client library against an alternate Ably environment (deprecated)
139140
# @option options [Symbol] :protocol (:msgpack) Protocol used to communicate with Ably, :json and :msgpack currently supported
140141
# @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
141142
# @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 +189,6 @@ def initialize(options)
188189
@agent = options.delete(:agent) || Ably::AGENT
189190
@realtime_client = options.delete(:realtime_client)
190191
@tls = options.delete_with_default(:tls, true)
191-
@environment = options.delete(:environment) # nil is production
192-
@environment = nil if [:production, 'production'].include?(@environment)
193192
@protocol = options.delete(:protocol) || :msgpack
194193
@debug_http = options.delete(:debug_http)
195194
@log_level = options.delete(:log_level) || ::Logger::WARN
@@ -203,18 +202,27 @@ def initialize(options)
203202
@max_frame_size = options.delete(:max_frame_size) || MAX_FRAME_SIZE
204203
@idempotent_rest_publishing = options.delete_with_default(:idempotent_rest_publishing, true)
205204

205+
@environment = options.delete(:environment) # nil is production
206+
@environment = nil if [:production, 'production'].include?(@environment)
207+
@endpoint = @environment || options.delete_with_default(:endpoint, 'main')
208+
206209
if options[:fallback_hosts_use_default] && options[:fallback_hosts]
207210
raise ArgumentError, "fallback_hosts_use_default cannot be set to try when fallback_hosts is also provided"
208211
end
212+
209213
@fallback_hosts = case
210214
when options.delete(:fallback_hosts_use_default)
211215
Ably::FALLBACK_HOSTS
212216
when options_fallback_hosts = options.delete(:fallback_hosts)
213217
options_fallback_hosts
214218
when custom_host || options[:realtime_host] || custom_port || custom_tls_port
215219
[]
216-
when environment
217-
CUSTOM_ENVIRONMENT_FALLBACKS_SUFFIXES.map { |host| "#{environment}#{host}" }
220+
when endpoint
221+
if endpoint.start_with?('nonprod:')
222+
CUSTOM_ENVIRONMENT_FALLBACKS_SUFFIXES.map { |host| "#{endpoint.gsub('nonprod:', '')}.#{host}" }
223+
else
224+
CUSTOM_ENVIRONMENT_FALLBACKS_SUFFIXES.map { |host| "#{endpoint}.#{host}" }
225+
end
218226
else
219227
Ably::FALLBACK_HOSTS
220228
end
@@ -426,10 +434,24 @@ def push
426434
@push ||= Push.new(self)
427435
end
428436

429-
# @!attribute [r] endpoint
437+
# @!attribute [r] hostname
438+
# @return [String] The primary hostname to connect to Ably
439+
def hostname
440+
if endpoint.include?('.') || endpoint.include?('::') || endpoint == 'localhost'
441+
return endpoint
442+
end
443+
444+
if endpoint.start_with?('nonprod:')
445+
"#{endpoint.gsub('nonprod:', '')}.realtime.#{root_domain}"
446+
else
447+
"#{endpoint}.realtime.#{root_domain}"
448+
end
449+
end
450+
451+
# @!attribute [r] uri
430452
# @return [URI::Generic] Default Ably REST endpoint used for all requests
431-
def endpoint
432-
endpoint_for_host(custom_host || [@environment, DOMAIN].compact.join('-'))
453+
def uri
454+
uri_for_host(custom_host || hostname)
433455
end
434456

435457
# @!attribute [r] logger
@@ -480,7 +502,7 @@ def connection(options = {})
480502
if options[:use_fallback]
481503
fallback_connection
482504
else
483-
@connection ||= Faraday.new(endpoint.to_s, connection_options)
505+
@connection ||= Faraday.new(uri.to_s, connection_options)
484506
end
485507
end
486508

@@ -493,7 +515,7 @@ def connection(options = {})
493515
# @api private
494516
def fallback_connection
495517
unless defined?(@fallback_connections) && @fallback_connections
496-
@fallback_connections = fallback_hosts.shuffle.map { |host| Faraday.new(endpoint_for_host(host).to_s, connection_options) }
518+
@fallback_connections = fallback_hosts.shuffle.map { |host| Faraday.new(uri_for_host(host).to_s, connection_options) }
497519
end
498520
@fallback_index ||= 0
499521

@@ -653,7 +675,7 @@ def reauthorize_on_authorization_failure
653675
end
654676
end
655677

656-
def endpoint_for_host(host)
678+
def uri_for_host(host)
657679
port = if use_tls?
658680
custom_tls_port
659681
else
@@ -671,6 +693,14 @@ def endpoint_for_host(host)
671693
URI::Generic.build(options)
672694
end
673695

696+
def root_domain
697+
if endpoint.start_with?('nonprod:')
698+
'ably-nonprod.net'
699+
else
700+
'ably.net'
701+
end
702+
end
703+
674704
# Return a Hash of connection options to initiate the Faraday::Connection with
675705
#
676706
# @return [Hash]

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/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

spec/acceptance/rest/auth_spec.rb

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ def request_body_includes(request, protocol, key, val)
6161
end
6262

6363
it 'creates a TokenRequest automatically and sends it to Ably to obtain a token', webmock: true do
64-
token_request_stub = stub_request(:post, "#{client.endpoint}/keys/#{key_name}/requestToken").
64+
token_request_stub = stub_request(:post, "#{client.uri}/keys/#{key_name}/requestToken").
6565
to_return(status: 201, body: serialize_body({}, protocol), headers: { 'Content-Type' => content_type })
6666
expect(auth).to receive(:create_token_request).and_call_original
6767
auth.request_token
@@ -90,7 +90,7 @@ def coerce_if_time_value(field_name, value, params = {})
9090

9191
let(:token_response) { {} }
9292
let!(:request_token_stub) do
93-
stub_request(:post, "#{client.endpoint}/keys/#{key_name}/requestToken").
93+
stub_request(:post, "#{client.uri}/keys/#{key_name}/requestToken").
9494
with do |request|
9595
request_body_includes(request, protocol, token_param, coerce_if_time_value(token_param, random, multiply: 1000))
9696
end.to_return(
@@ -121,7 +121,7 @@ def coerce_if_time_value(field_name, value, params = {})
121121

122122
let(:token_response) { {} }
123123
let!(:request_token_stub) do
124-
stub_request(:post, "#{client.endpoint}/keys/#{key_name}/requestToken").
124+
stub_request(:post, "#{client.uri}/keys/#{key_name}/requestToken").
125125
with do |request|
126126
request_body_includes(request, protocol, 'mac', mac)
127127
end.to_return(
@@ -151,7 +151,7 @@ def coerce_if_time_value(field_name, value, params = {})
151151

152152
let(:token_response) { {} }
153153
let!(:request_token_stub) do
154-
stub_request(:post, "#{client.endpoint}/keys/#{key_name}/requestToken").
154+
stub_request(:post, "#{client.uri}/keys/#{key_name}/requestToken").
155155
with do |request|
156156
request_body_includes(request, protocol, 'mac', mac)
157157
end.to_return(
@@ -293,7 +293,7 @@ def coerce_if_time_value(field_name, value, params = {})
293293
let(:auth_url_content_type) { 'application/json' }
294294

295295
let!(:request_token_stub) do
296-
stub_request(:post, "#{client.endpoint}/keys/#{key_name}/requestToken").
296+
stub_request(:post, "#{client.uri}/keys/#{key_name}/requestToken").
297297
with do |request|
298298
request_body_includes(request, protocol, 'key_name', key_name)
299299
end.to_return(

spec/acceptance/rest/base_spec.rb

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
let(:body_value) { [as_since_epoch(now)] }
1515

1616
before do
17-
stub_request(:get, "#{client.endpoint}/time").
17+
stub_request(:get, "#{client.uri}/time").
1818
with(:headers => { 'Accept' => mime }).
1919
to_return(:status => 200, :body => request_body, :headers => { 'Content-Type' => mime })
2020
end
@@ -87,7 +87,7 @@
8787
let(:error_response) { '{ "error": { "statusCode": 500, "code": 50000, "message": "Internal error" } }' }
8888

8989
before do
90-
(client.fallback_hosts.map { |host| "https://#{host}" } + [client.endpoint]).each do |host|
90+
(client.fallback_hosts.map { |host| "https://#{host}" } + [client.uri]).each do |host|
9191
stub_request(:get, "#{host}/time")
9292
.to_return(:status => 500, :body => error_response, :headers => { 'Content-Type' => 'application/json' })
9393
end
@@ -100,7 +100,7 @@
100100

101101
describe '500 server error without a valid JSON response body', :webmock do
102102
before do
103-
(client.fallback_hosts.map { |host| "https://#{host}" } + [client.endpoint]).each do |host|
103+
(client.fallback_hosts.map { |host| "https://#{host}" } + [client.uri]).each do |host|
104104
stub_request(:get, "#{host}/time").
105105
to_return(:status => 500, :headers => { 'Content-Type' => 'application/json' })
106106
end
@@ -121,15 +121,15 @@
121121
@token_requests = 0
122122
@publish_attempts = 0
123123

124-
stub_request(:post, "#{client.endpoint}/keys/#{key_name}/requestToken").to_return do
124+
stub_request(:post, "#{client.uri}/keys/#{key_name}/requestToken").to_return do
125125
@token_requests += 1
126126
{
127127
:body => public_send("token_#{@token_requests}").merge(expires: (Time.now.to_i + 60) * 1000).to_json,
128128
:headers => { 'Content-Type' => 'application/json' }
129129
}
130130
end
131131

132-
stub_request(:post, "#{client.endpoint}/channels/#{channel}/publish").to_return do
132+
stub_request(:post, "#{client.uri}/channels/#{channel}/publish").to_return do
133133
@publish_attempts += 1
134134
if [1, 3].include?(@publish_attempts)
135135
{ status: 201, :body => '[]', :headers => { 'Content-Type' => 'application/json' } }

0 commit comments

Comments
 (0)