From bae5bc3958d3c5f5d96c7b81435231e88e569e13 Mon Sep 17 00:00:00 2001 From: Laura Martin Date: Fri, 31 Jan 2025 12:41:27 +0000 Subject: [PATCH] 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 --- lib/ably/modules/ably.rb | 8 +-- lib/ably/realtime/client.rb | 35 +++++++++-- lib/ably/realtime/connection.rb | 10 ++-- lib/ably/rest/client.rb | 60 +++++++++++++++---- spec/acceptance/realtime/client_spec.rb | 10 ++-- spec/acceptance/realtime/message_spec.rb | 2 +- spec/acceptance/realtime/push_admin_spec.rb | 4 +- spec/acceptance/rest/auth_spec.rb | 10 ++-- spec/acceptance/rest/base_spec.rb | 10 ++-- spec/acceptance/rest/channel_spec.rb | 10 ++-- spec/acceptance/rest/client_spec.rb | 38 ++++++------ spec/acceptance/rest/presence_spec.rb | 30 +++++----- spec/acceptance/rest/push_admin_spec.rb | 4 +- spec/shared/client_initializer_behaviour.rb | 34 ++++------- spec/support/test_app.rb | 11 ++-- .../unit/models/http_paginated_result_spec.rb | 4 +- spec/unit/realtime/connection_spec.rb | 2 +- 17 files changed, 168 insertions(+), 114 deletions(-) diff --git a/lib/ably/modules/ably.rb b/lib/ably/modules/ably.rb index 0cb5ef9ac..0c277617a 100644 --- a/lib/ably/modules/ably.rb +++ b/lib/ably/modules/ably.rb @@ -11,12 +11,12 @@ module Ably FALLBACK_DOMAIN = 'ably-realtime.com'.freeze FALLBACK_IDS = %w(a b c d e).freeze - # Default production fallbacks a.ably-realtime.com ... e.ably-realtime.com - FALLBACK_HOSTS = FALLBACK_IDS.map { |host| "#{host}.#{FALLBACK_DOMAIN}".freeze }.freeze + # Default production fallbacks main.a.fallback.ably-realtime.com ... main.e.fallback.ably-realtime.com + FALLBACK_HOSTS = FALLBACK_IDS.map { |host| "main.#{host}.fallback.#{FALLBACK_DOMAIN}".freeze }.freeze - # Custom environment default fallbacks {ENV}-a-fallback.ably-realtime.com ... {ENV}-a-fallback.ably-realtime.com + # Custom environment default fallbacks {ENV}.a.fallback.ably-realtime.com ... {ENV}.e.fallback.ably-realtime.com CUSTOM_ENVIRONMENT_FALLBACKS_SUFFIXES = FALLBACK_IDS.map do |host| - "-#{host}-fallback.#{FALLBACK_DOMAIN}".freeze + ".#{host}.fallback.#{FALLBACK_DOMAIN}".freeze end.freeze INTERNET_CHECK = { diff --git a/lib/ably/realtime/client.rb b/lib/ably/realtime/client.rb index b35aaa94b..12f6571b4 100644 --- a/lib/ably/realtime/client.rb +++ b/lib/ably/realtime/client.rb @@ -73,7 +73,7 @@ class Client def_delegators :auth, :client_id, :auth_options def_delegators :@rest_client, :encoders def_delegators :@rest_client, :use_tls?, :protocol, :protocol_binary? - def_delegators :@rest_client, :environment, :custom_host, :custom_port, :custom_tls_port + def_delegators :@rest_client, :endpoint, :environment, :custom_host, :custom_port, :custom_tls_port def_delegators :@rest_client, :log_level def_delegators :@rest_client, :options @@ -289,10 +289,34 @@ def publish(channel_name, name, data = nil, attributes = {}, &success_block) end end - # @!attribute [r] endpoint + # @!attribute [r] hostname + # @return [String] The primary hostname to connect to Ably + def hostname + if endpoint.include?('.') || endpoint.include?('::') || endpoint == 'localhost' + return endpoint + end + + if endpoint.start_with?('nonprod:') + "#{endpoint.gsub('nonprod:', '')}.realtime.#{root_domain}" + else + "#{endpoint}.realtime.#{root_domain}" + end + end + + # @!attribute [r] root_domain + # @return [String] The root domain used in the hostname + def root_domain + if endpoint.start_with?('nonprod:') + 'ably-nonprod.net' + else + 'ably.net' + end + end + + # @!attribute [r] uri # @return [URI::Generic] Default Ably Realtime endpoint used for all requests - def endpoint - endpoint_for_host(custom_realtime_host || [environment, DOMAIN].compact.join('-')) + def uri + uri_for_host(custom_realtime_host || hostname) end # (see Ably::Rest::Client#register_encoder) @@ -341,7 +365,8 @@ def device end private - def endpoint_for_host(host) + + def uri_for_host(host) port = if use_tls? custom_tls_port else diff --git a/lib/ably/realtime/connection.rb b/lib/ably/realtime/connection.rb index 6295ef09e..82ddb5990 100644 --- a/lib/ably/realtime/connection.rb +++ b/lib/ably/realtime/connection.rb @@ -173,7 +173,7 @@ def initialize(client, options) @state = STATE(state_machine.current_state) @manager = ConnectionManager.new(self) - @current_host = client.endpoint.host + @current_host = client.uri.host reset_client_msg_serial end @@ -396,12 +396,12 @@ def determine_host @current_host = if internet_is_up_result client.fallback_endpoint.host else - client.endpoint.host + client.uri.host end yield current_host end else - @current_host = client.endpoint.host + @current_host = client.uri.host yield current_host end end @@ -496,8 +496,8 @@ def create_websocket_transport end end - url = URI(client.endpoint).tap do |endpoint| - endpoint.query = URI.encode_www_form(url_params) + url = URI(client.uri).tap do |uri| + uri.query = URI.encode_www_form(url_params) end determine_host do |host| diff --git a/lib/ably/rest/client.rb b/lib/ably/rest/client.rb index 3dbda2dfb..f4e5ff029 100644 --- a/lib/ably/rest/client.rb +++ b/lib/ably/rest/client.rb @@ -43,7 +43,11 @@ class Client def_delegators :auth, :client_id, :auth_options - # Custom environment to use such as 'sandbox' when testing the client library against an alternate Ably environment + # The hostname used to connect to Ably + # @return [String] + attr_reader :endpoint + + # Custom environment to use such as 'sandbox' when testing the client library against an alternate Ably environment (deprecated) # @return [String] attr_reader :environment @@ -135,7 +139,8 @@ class Client # @option options [String] :token Token string or {Models::TokenDetails} used to authenticate requests # @option options [String] :token_details {Models::TokenDetails} used to authenticate requests # @option options [Boolean] :use_token_auth Will force Basic Auth if set to false, and Token auth if set to true - # @option options [String] :environment Specify 'sandbox' when testing the client library against an alternate Ably environment + # @option options [String] :endpoint Specify a routing policy or fully-qualified domain name to connect to Ably + # @option options [String] :environment Specify 'sandbox' when testing the client library against an alternate Ably environment (deprecated) # @option options [Symbol] :protocol (:msgpack) Protocol used to communicate with Ably, :json and :msgpack currently supported # @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 # @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 +193,6 @@ def initialize(options) @agent = options.delete(:agent) || Ably::AGENT @realtime_client = options.delete(:realtime_client) @tls = options.delete_with_default(:tls, true) - @environment = options.delete(:environment) # nil is production - @environment = nil if [:production, 'production'].include?(@environment) @protocol = options.delete(:protocol) || :msgpack @debug_http = options.delete(:debug_http) @log_level = options.delete(:log_level) || ::Logger::WARN @@ -203,9 +206,14 @@ def initialize(options) @max_frame_size = options.delete(:max_frame_size) || MAX_FRAME_SIZE @idempotent_rest_publishing = options.delete_with_default(:idempotent_rest_publishing, true) + @environment = options.delete(:environment) # nil is production + @environment = nil if [:production, 'production'].include?(@environment) + @endpoint = environment || options.delete_with_default(:endpoint, 'main') + if options[:fallback_hosts_use_default] && options[:fallback_hosts] raise ArgumentError, "fallback_hosts_use_default cannot be set to try when fallback_hosts is also provided" end + @fallback_hosts = case when options.delete(:fallback_hosts_use_default) Ably::FALLBACK_HOSTS @@ -213,8 +221,12 @@ def initialize(options) options_fallback_hosts when custom_host || options[:realtime_host] || custom_port || custom_tls_port [] - when environment - CUSTOM_ENVIRONMENT_FALLBACKS_SUFFIXES.map { |host| "#{environment}#{host}" } + when endpoint + if endpoint.start_with?('nonprod:') + CUSTOM_ENVIRONMENT_FALLBACKS_SUFFIXES.map { |host| "#{endpoint.gsub('nonprod:', '')}.#{host}" } + else + CUSTOM_ENVIRONMENT_FALLBACKS_SUFFIXES.map { |host| "#{endpoint}.#{host}" } + end else Ably::FALLBACK_HOSTS end @@ -426,10 +438,34 @@ def push @push ||= Push.new(self) end - # @!attribute [r] endpoint + # @!attribute [r] hostname + # @return [String] The primary hostname to connect to Ably + def hostname + if endpoint.include?('.') || endpoint.include?('::') || endpoint == 'localhost' + return endpoint + end + + if endpoint.start_with?('nonprod:') + "#{endpoint.gsub('nonprod:', '')}.realtime.#{root_domain}" + else + "#{endpoint}.realtime.#{root_domain}" + end + end + + # @!attribute [r] root_domain + # @return [String] The root domain used in the hostname + def root_domain + if endpoint.start_with?('nonprod:') + 'ably-nonprod.net' + else + 'ably.net' + end + end + + # @!attribute [r] uri # @return [URI::Generic] Default Ably REST endpoint used for all requests - def endpoint - endpoint_for_host(custom_host || [@environment, DOMAIN].compact.join('-')) + def uri + uri_for_host(custom_host || hostname) end # @!attribute [r] logger @@ -480,7 +516,7 @@ def connection(options = {}) if options[:use_fallback] fallback_connection else - @connection ||= Faraday.new(endpoint.to_s, connection_options) + @connection ||= Faraday.new(uri.to_s, connection_options) end end @@ -493,7 +529,7 @@ def connection(options = {}) # @api private def fallback_connection unless defined?(@fallback_connections) && @fallback_connections - @fallback_connections = fallback_hosts.shuffle.map { |host| Faraday.new(endpoint_for_host(host).to_s, connection_options) } + @fallback_connections = fallback_hosts.shuffle.map { |host| Faraday.new(uri_for_host(host).to_s, connection_options) } end @fallback_index ||= 0 @@ -653,7 +689,7 @@ def reauthorize_on_authorization_failure end end - def endpoint_for_host(host) + def uri_for_host(host) port = if use_tls? custom_tls_port else diff --git a/spec/acceptance/realtime/client_spec.rb b/spec/acceptance/realtime/client_spec.rb index b392d118e..ce26dd30c 100644 --- a/spec/acceptance/realtime/client_spec.rb +++ b/spec/acceptance/realtime/client_spec.rb @@ -234,7 +234,7 @@ context '#request (#RSC19*)' do let(:client_options) { default_options.merge(key: api_key) } let(:device_id) { random_str } - let(:endpoint) { subject.rest_client.endpoint } + let(:uri) { subject.rest_client.uri } context 'get' do it 'returns an HttpPaginatedResponse object' do @@ -287,7 +287,7 @@ context 'post', :webmock do before do - stub_request(:delete, "#{endpoint}/push/deviceRegistrations/#{device_id}/resetUpdateToken"). + stub_request(:delete, "#{uri}/push/deviceRegistrations/#{device_id}/resetUpdateToken"). to_return(status: 200, body: '{}', headers: { 'Content-Type' => 'application/json' }) end @@ -301,7 +301,7 @@ context 'delete', :webmock do before do - stub_request(:delete, "#{endpoint}/push/channelSubscriptions?deviceId=#{device_id}"). + stub_request(:delete, "#{uri}/push/channelSubscriptions?deviceId=#{device_id}"). to_return(status: 200, body: '{}', headers: { 'Content-Type' => 'application/json' }) end @@ -317,7 +317,7 @@ let(:body_params) { { 'metadata' => { 'key' => 'value' } } } before do - stub_request(:patch, "#{endpoint}/push/deviceRegistrations/#{device_id}") + stub_request(:patch, "#{uri}/push/deviceRegistrations/#{device_id}") .with(body: serialize_body(body_params, protocol)) .to_return(status: 200, body: '{}', headers: { 'Content-Type' => 'application/json' }) end @@ -341,7 +341,7 @@ end before do - stub_request(:put, "#{endpoint}/push/deviceRegistrations/#{device_id}") + stub_request(:put, "#{uri}/push/deviceRegistrations/#{device_id}") .with(body: serialize_body(body_params, protocol)) .to_return(status: 200, body: '{}', headers: { 'Content-Type' => 'application/json' }) end diff --git a/spec/acceptance/realtime/message_spec.rb b/spec/acceptance/realtime/message_spec.rb index b218a8e13..63c8aa351 100644 --- a/spec/acceptance/realtime/message_spec.rb +++ b/spec/acceptance/realtime/message_spec.rb @@ -775,7 +775,7 @@ def publish_and_check_extras(extras) EventMachine.add_timer(0.0001) do connection.transition_state_machine :suspended stub_const 'Ably::FALLBACK_HOSTS', [] - allow(client).to receive(:endpoint).and_return(URI::Generic.build(scheme: 'wss', host: 'does.not.exist.com')) + allow(client).to receive(:uri).and_return(URI::Generic.build(scheme: 'wss', host: 'does.not.exist.com')) end end end diff --git a/spec/acceptance/realtime/push_admin_spec.rb b/spec/acceptance/realtime/push_admin_spec.rb index e9dfba92b..1be579682 100644 --- a/spec/acceptance/realtime/push_admin_spec.rb +++ b/spec/acceptance/realtime/push_admin_spec.rb @@ -102,7 +102,7 @@ end let!(:publish_stub) do - stub_request(:post, "#{client.rest_client.endpoint}/push/publish"). + stub_request(:post, "#{client.rest_client.uri}/push/publish"). with do |request| expect(deserialize_body(request.body, protocol)['recipient']['camelCase']['secondLevelCamelCase']).to eql('val') expect(deserialize_body(request.body, protocol)['recipient']).to_not have_key('camel_case') @@ -135,7 +135,7 @@ 'transportType' => 'ablyChannel', 'channel' => channel, 'ablyKey' => api_key, - 'ablyUrl' => client.rest_client.endpoint.to_s + 'ablyUrl' => client.rest_client.uri.to_s } end let(:notification_payload) do diff --git a/spec/acceptance/rest/auth_spec.rb b/spec/acceptance/rest/auth_spec.rb index ef07af9f2..6b34e8f1c 100644 --- a/spec/acceptance/rest/auth_spec.rb +++ b/spec/acceptance/rest/auth_spec.rb @@ -61,7 +61,7 @@ def request_body_includes(request, protocol, key, val) end it 'creates a TokenRequest automatically and sends it to Ably to obtain a token', webmock: true do - token_request_stub = stub_request(:post, "#{client.endpoint}/keys/#{key_name}/requestToken"). + token_request_stub = stub_request(:post, "#{client.uri}/keys/#{key_name}/requestToken"). to_return(status: 201, body: serialize_body({}, protocol), headers: { 'Content-Type' => content_type }) expect(auth).to receive(:create_token_request).and_call_original auth.request_token @@ -90,7 +90,7 @@ def coerce_if_time_value(field_name, value, params = {}) let(:token_response) { {} } let!(:request_token_stub) do - stub_request(:post, "#{client.endpoint}/keys/#{key_name}/requestToken"). + stub_request(:post, "#{client.uri}/keys/#{key_name}/requestToken"). with do |request| request_body_includes(request, protocol, token_param, coerce_if_time_value(token_param, random, multiply: 1000)) end.to_return( @@ -121,7 +121,7 @@ def coerce_if_time_value(field_name, value, params = {}) let(:token_response) { {} } let!(:request_token_stub) do - stub_request(:post, "#{client.endpoint}/keys/#{key_name}/requestToken"). + stub_request(:post, "#{client.uri}/keys/#{key_name}/requestToken"). with do |request| request_body_includes(request, protocol, 'mac', mac) end.to_return( @@ -151,7 +151,7 @@ def coerce_if_time_value(field_name, value, params = {}) let(:token_response) { {} } let!(:request_token_stub) do - stub_request(:post, "#{client.endpoint}/keys/#{key_name}/requestToken"). + stub_request(:post, "#{client.uri}/keys/#{key_name}/requestToken"). with do |request| request_body_includes(request, protocol, 'mac', mac) end.to_return( @@ -293,7 +293,7 @@ def coerce_if_time_value(field_name, value, params = {}) let(:auth_url_content_type) { 'application/json' } let!(:request_token_stub) do - stub_request(:post, "#{client.endpoint}/keys/#{key_name}/requestToken"). + stub_request(:post, "#{client.uri}/keys/#{key_name}/requestToken"). with do |request| request_body_includes(request, protocol, 'key_name', key_name) end.to_return( diff --git a/spec/acceptance/rest/base_spec.rb b/spec/acceptance/rest/base_spec.rb index 0e046458a..4a8ff749f 100644 --- a/spec/acceptance/rest/base_spec.rb +++ b/spec/acceptance/rest/base_spec.rb @@ -14,7 +14,7 @@ let(:body_value) { [as_since_epoch(now)] } before do - stub_request(:get, "#{client.endpoint}/time"). + stub_request(:get, "#{client.uri}/time"). with(:headers => { 'Accept' => mime }). to_return(:status => 200, :body => request_body, :headers => { 'Content-Type' => mime }) end @@ -87,7 +87,7 @@ let(:error_response) { '{ "error": { "statusCode": 500, "code": 50000, "message": "Internal error" } }' } before do - (client.fallback_hosts.map { |host| "https://#{host}" } + [client.endpoint]).each do |host| + (client.fallback_hosts.map { |host| "https://#{host}" } + [client.uri]).each do |host| stub_request(:get, "#{host}/time") .to_return(:status => 500, :body => error_response, :headers => { 'Content-Type' => 'application/json' }) end @@ -100,7 +100,7 @@ describe '500 server error without a valid JSON response body', :webmock do before do - (client.fallback_hosts.map { |host| "https://#{host}" } + [client.endpoint]).each do |host| + (client.fallback_hosts.map { |host| "https://#{host}" } + [client.uri]).each do |host| stub_request(:get, "#{host}/time"). to_return(:status => 500, :headers => { 'Content-Type' => 'application/json' }) end @@ -121,7 +121,7 @@ @token_requests = 0 @publish_attempts = 0 - stub_request(:post, "#{client.endpoint}/keys/#{key_name}/requestToken").to_return do + stub_request(:post, "#{client.uri}/keys/#{key_name}/requestToken").to_return do @token_requests += 1 { :body => public_send("token_#{@token_requests}").merge(expires: (Time.now.to_i + 60) * 1000).to_json, @@ -129,7 +129,7 @@ } end - stub_request(:post, "#{client.endpoint}/channels/#{channel}/publish").to_return do + stub_request(:post, "#{client.uri}/channels/#{channel}/publish").to_return do @publish_attempts += 1 if [1, 3].include?(@publish_attempts) { status: 201, :body => '[]', :headers => { 'Content-Type' => 'application/json' } } diff --git a/spec/acceptance/rest/channel_spec.rb b/spec/acceptance/rest/channel_spec.rb index 37a4e1748..36d15eab2 100644 --- a/spec/acceptance/rest/channel_spec.rb +++ b/spec/acceptance/rest/channel_spec.rb @@ -364,12 +364,12 @@ context 'with a non ASCII channel name' do let(:channel_name) { 'foo:¡€≤`☃' } let(:channel_name_encoded) { 'foo%3A%C2%A1%E2%82%AC%E2%89%A4%60%E2%98%83' } - let(:endpoint) { client.endpoint } + let(:uri) { client.uri } let(:channel) { client.channels.get(channel_name) } context 'stubbed', :webmock do let!(:get_stub) { - stub_request(:post, "#{endpoint}/channels/#{channel_name_encoded}/publish"). + stub_request(:post, "#{uri}/channels/#{channel_name_encoded}/publish"). to_return(:body => '{}', :headers => { 'Content-Type' => 'application/json' }) } @@ -534,8 +534,8 @@ describe '#history option' do let(:channel_name) { "persisted:#{random_str(4)}" } let(:channel) { client.channel(channel_name) } - let(:endpoint) do - client.endpoint + let(:uri) do + client.uri end let(:default_history_options) do { @@ -549,7 +549,7 @@ let!(:history_stub) { query_params = default_history_options .merge(option => milliseconds).map { |k, v| "#{k}=#{v}" }.join('&') - stub_request(:get, "#{endpoint}/channels/#{URI.encode_www_form_component(channel_name)}/messages?#{query_params}"). + stub_request(:get, "#{uri}/channels/#{URI.encode_www_form_component(channel_name)}/messages?#{query_params}"). to_return(:body => '{}', :headers => { 'Content-Type' => 'application/json' }) } diff --git a/spec/acceptance/rest/client_spec.rb b/spec/acceptance/rest/client_spec.rb index e1619c6ef..8d9d38de9 100644 --- a/spec/acceptance/rest/client_spec.rb +++ b/spec/acceptance/rest/client_spec.rb @@ -301,12 +301,12 @@ def encode64(text) context 'configured' do let(:client_options) { default_options.merge(key: api_key, environment: 'production') } - 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 (#RSC15a)' do + it 'should make connection attempts to main.a.fallback.ably-realtime.com, main.b.fallback.ably-realtime.com, main.c.fallback.ably-realtime.com, main.d.fallback.ably-realtime.com, main.e.fallback.ably-realtime.com (#RSC15a)' do hosts = [] 5.times do hosts << client.fallback_connection.host end - expect(hosts).to match_array(%w(a.ably-realtime.com b.ably-realtime.com c.ably-realtime.com d.ably-realtime.com e.ably-realtime.com)) + expect(hosts).to match_array(%w(main.a.fallback.ably-realtime.com main.b.fallback.ably-realtime.com main.c.fallback.ably-realtime.com main.d.fallback.ably-realtime.com main.e.fallback.ably-realtime.com)) end end @@ -327,12 +327,12 @@ def encode64(text) context 'and no custom fallback hosts are provided' do let(:client_options) { default_options.merge(environment: 'sandbox', key: api_key) } - it 'should make connection attempts to sandbox-a-fallback.ably-realtime.com, sandbox-b-fallback.ably-realtime.com, sandbox-c-fallback.ably-realtime.com, sandbox-d-fallback.ably-realtime.com, sandbox-e-fallback.ably-realtime.com (#RSC15a)' do + it 'should make connection attempts to sandbox.a.fallback.ably-realtime.com, sandbox.b.fallback.ably-realtime.com, sandbox.c.fallback.ably-realtime.com, sandbox.d.fallback.ably-realtime.com, sandbox.e.fallback.ably-realtime.com (#RSC15a)' do hosts = [] 5.times do hosts << client.fallback_connection.host end - expect(hosts).to match_array(%w(a b c d e).map { |id| "sandbox-#{id}-fallback.ably-realtime.com" }) + expect(hosts).to match_array(%w(a b c d e).map { |id| "sandbox.#{id}.fallback.ably-realtime.com" }) end end end @@ -554,8 +554,8 @@ def encode64(text) end context 'using a local web-server', webmock: false do - let(:primary_host) { 'local-rest.ably.io' } - let(:fallbacks) { ['local.ably.io', 'localhost'] } + let(:primary_host) { 'local.realtime.ably.net' } + let(:fallbacks) { ['local.ably.net', 'localhost'] } let(:port) { rand(10000) + 2000 } let(:channel_name) { 'foo' } let(:request_timeout) { 3 } @@ -838,7 +838,7 @@ def encode64(text) context 'when environment is not production and server returns a 50x error' do let(:env) { 'custom-env' } - let(:default_fallbacks) { %w(a b c d e).map { |id| "#{env}-#{id}-fallback.ably-realtime.com" } } + let(:default_fallbacks) { %w(a b c d e).map { |id| "#{env}.#{id}.fallback.ably-realtime.com" } } let(:custom_hosts) { %w(A.foo.com B.foo.com) } let(:max_retry_count) { 2 } let(:max_retry_duration) { 0.5 } @@ -1086,7 +1086,7 @@ def encode64(text) let(:client_options) { default_options.merge(key: api_key, agent: agent) } let!(:publish_message_stub) do - stub_request(:post, "#{client.endpoint}/channels/foo/publish"). + stub_request(:post, "#{client.uri}/channels/foo/publish"). with(headers: { 'X-Ably-Version' => Ably::PROTOCOL_VERSION, 'Ably-Agent' => agent || Ably::AGENT @@ -1113,7 +1113,7 @@ def encode64(text) context '#request (#RSC19*, #TO3l9)' do let(:client_options) { default_options.merge(key: api_key) } let(:device_id) { random_str } - let(:endpoint) { client.endpoint } + let(:uri) { client.uri } context 'get' do it 'returns an HttpPaginatedResponse object' do @@ -1156,7 +1156,7 @@ def encode64(text) context 'post', :webmock do before do - stub_request(:delete, "#{endpoint}/push/deviceRegistrations/#{device_id}/resetUpdateToken"). + stub_request(:delete, "#{uri}/push/deviceRegistrations/#{device_id}/resetUpdateToken"). to_return(status: 200, body: '{}', headers: { 'Content-Type' => 'application/json' }) end @@ -1168,14 +1168,14 @@ def encode64(text) it 'raises an exception once body size in bytes exceeded' do expect { - client.request(:post, endpoint, {}, { content: 'x' * Ably::Rest::Client::MAX_FRAME_SIZE }) + client.request(:post, uri, {}, { content: 'x' * Ably::Rest::Client::MAX_FRAME_SIZE }) }.to raise_error(Ably::Exceptions::MaxFrameSizeExceeded) end end context 'delete', :webmock do before do - stub_request(:delete, "#{endpoint}/push/channelSubscriptions?deviceId=#{device_id}"). + stub_request(:delete, "#{uri}/push/channelSubscriptions?deviceId=#{device_id}"). to_return(status: 200, body: '{}', headers: { 'Content-Type' => 'application/json' }) end @@ -1190,7 +1190,7 @@ def encode64(text) let(:body_params) { { 'metadata' => { 'key' => 'value' } } } before do - stub_request(:patch, "#{endpoint}/push/deviceRegistrations/#{device_id}") + stub_request(:patch, "#{uri}/push/deviceRegistrations/#{device_id}") .with(body: serialize_body(body_params, protocol)) .to_return(status: 200, body: '{}', headers: { 'Content-Type' => 'application/json' }) end @@ -1203,7 +1203,7 @@ def encode64(text) it 'raises an exception once body size in bytes exceeded' do expect { - client.request(:patch, endpoint, {}, { content: 'x' * Ably::Rest::Client::MAX_FRAME_SIZE }) + client.request(:patch, uri, {}, { content: 'x' * Ably::Rest::Client::MAX_FRAME_SIZE }) }.to raise_error(Ably::Exceptions::MaxFrameSizeExceeded) end end @@ -1219,7 +1219,7 @@ def encode64(text) end before do - stub_request(:put, "#{endpoint}/push/deviceRegistrations/#{device_id}") + stub_request(:put, "#{uri}/push/deviceRegistrations/#{device_id}") .with(body: serialize_body(body_params, protocol)) .to_return(status: 200, body: '{}', headers: { 'Content-Type' => 'application/json' }) end @@ -1232,7 +1232,7 @@ def encode64(text) it 'raises an exception once body size in bytes exceeded' do expect { - client.request(:put, endpoint, {}, { content: 'x' * Ably::Rest::Client::MAX_FRAME_SIZE }) + client.request(:put, uri, {}, { content: 'x' * Ably::Rest::Client::MAX_FRAME_SIZE }) }.to raise_error(Ably::Exceptions::MaxFrameSizeExceeded) end end @@ -1246,7 +1246,7 @@ def encode64(text) before do @request_id = nil - stub_request(:get, Addressable::Template.new("#{client.endpoint}/time{?request_id}")).with do |request| + stub_request(:get, Addressable::Template.new("#{client.uri}/time{?request_id}")).with do |request| @request_id = request.uri.query_values['request_id'] end.to_return do raise Faraday::TimeoutError.new('timeout error message') @@ -1268,7 +1268,7 @@ def encode64(text) context 'with mocks to inspect the params', :webmock do before do - stub_request(:post, Addressable::Template.new("#{client.endpoint}/channels/#{channel_name}/publish{?request_id}")). + stub_request(:post, Addressable::Template.new("#{client.uri}/channels/#{channel_name}/publish{?request_id}")). with do |request| @request_id = request.uri.query_values['request_id'] end.to_return(:status => 200, :body => [], :headers => { 'Content-Type' => 'application/json' }) @@ -1312,7 +1312,7 @@ def encode64(text) before do @request_id = nil - hosts = Ably::FALLBACK_HOSTS + ['rest.ably.io'] + hosts = Ably::FALLBACK_HOSTS + ['main.realtime.ably.net'] hosts.each do |host| stub_request(:get, Addressable::Template.new("https://#{host.downcase}/time{?request_id}")).with do |request| @request_id = request.uri.query_values['request_id'] diff --git a/spec/acceptance/rest/presence_spec.rb b/spec/acceptance/rest/presence_spec.rb index b0c61c8e0..bd76f460b 100644 --- a/spec/acceptance/rest/presence_spec.rb +++ b/spec/acceptance/rest/presence_spec.rb @@ -68,12 +68,12 @@ limit: 100 } end - let(:endpoint) do - client.endpoint + let(:uri) do + client.uri end let!(:get_stub) { query_params = query_options.map { |k, v| "#{k}=#{v}" }.join('&') - stub_request(:get, "#{endpoint}/channels/#{URI.encode_www_form_component(channel_name)}/presence?#{query_params}"). + stub_request(:get, "#{uri}/channels/#{URI.encode_www_form_component(channel_name)}/presence?#{query_params}"). to_return(:body => '{}', :headers => { 'Content-Type' => 'application/json' }) } let(:channel_name) { random_str } @@ -115,12 +115,12 @@ context 'with a non ASCII channel name' do let(:channel_name) { 'foo:¡€≤`☃' } let(:channel_name_encoded) { 'foo%3A%C2%A1%E2%82%AC%E2%89%A4%60%E2%98%83' } - let(:endpoint) { client.endpoint } + let(:uri) { client.uri } let(:channel) { client.channels.get(channel_name) } context 'stubbed', :webmock do let!(:get_stub) { - stub_request(:get, "#{endpoint}/channels/#{channel_name_encoded}/presence?limit=100"). + stub_request(:get, "#{uri}/channels/#{channel_name_encoded}/presence?limit=100"). to_return(:body => '{}', :headers => { 'Content-Type' => 'application/json' }) } @@ -197,8 +197,8 @@ let(:presence) { client.channel(channel_name).presence } let(:user) { 'appid.keyuid' } let(:secret) { random_str(8) } - let(:endpoint) do - client.endpoint + let(:uri) do + client.uri end let(:client) do Ably::Rest::Client.new(key: "#{user}:#{secret}") @@ -213,7 +213,7 @@ context 'limit options', :webmock do let!(:history_stub) { query_params = history_options.map { |k, v| "#{k}=#{v}" }.join('&') - stub_request(:get, "#{endpoint}/channels/#{URI.encode_www_form_component(channel_name)}/presence/history?#{query_params}"). + stub_request(:get, "#{uri}/channels/#{URI.encode_www_form_component(channel_name)}/presence/history?#{query_params}"). to_return(:body => '{}', :headers => { 'Content-Type' => 'application/json' }) } @@ -253,7 +253,7 @@ } let!(:history_stub) { query_params = history_options.map { |k, v| "#{k}=#{v}" }.join('&') - stub_request(:get, "#{endpoint}/channels/#{URI.encode_www_form_component(channel_name)}/presence/history?#{query_params}"). + stub_request(:get, "#{uri}/channels/#{URI.encode_www_form_component(channel_name)}/presence/history?#{query_params}"). to_return(:body => '{}', :headers => { 'Content-Type' => 'application/json' }) } @@ -322,8 +322,8 @@ def message(client_id, messages) describe 'decoding permutations using mocked #history', :webmock do let(:user) { 'appid.keyuid' } let(:secret) { random_str(8) } - let(:endpoint) do - client.endpoint + let(:uri) do + client.uri end let(:client) do Ably::Rest::Client.new(client_options.merge(key: "#{user}:#{secret}")) @@ -357,7 +357,7 @@ def message(client_id, messages) context '#get' do let!(:get_stub) { - stub_request(:get, "#{endpoint}/channels/#{URI.encode_www_form_component(channel_name)}/presence?limit=100"). + stub_request(:get, "#{uri}/channels/#{URI.encode_www_form_component(channel_name)}/presence?limit=100"). to_return(:body => serialized_encoded_message, :headers => { 'Content-Type' => content_type }) } @@ -374,7 +374,7 @@ def message(client_id, messages) context '#history' do let!(:history_stub) { - stub_request(:get, "#{endpoint}/channels/#{URI.encode_www_form_component(channel_name)}/presence/history?direction=backwards&limit=100"). + stub_request(:get, "#{uri}/channels/#{URI.encode_www_form_component(channel_name)}/presence/history?direction=backwards&limit=100"). to_return(:body => serialized_encoded_message, :headers => { 'Content-Type' => content_type }) } @@ -404,7 +404,7 @@ def message(client_id, messages) context '#get' do let(:client_options) { default_options.merge(log_level: :fatal) } let!(:get_stub) { - stub_request(:get, "#{endpoint}/channels/#{URI.encode_www_form_component(channel_name)}/presence?limit=100"). + stub_request(:get, "#{uri}/channels/#{URI.encode_www_form_component(channel_name)}/presence?limit=100"). to_return(:body => serialized_encoded_message_with_invalid_encoding, :headers => { 'Content-Type' => content_type }) } let(:presence_message) { presence.get.items.first } @@ -428,7 +428,7 @@ def message(client_id, messages) context '#history' do let(:client_options) { default_options.merge(log_level: :fatal) } let!(:history_stub) { - stub_request(:get, "#{endpoint}/channels/#{URI.encode_www_form_component(channel_name)}/presence/history?direction=backwards&limit=100"). + stub_request(:get, "#{uri}/channels/#{URI.encode_www_form_component(channel_name)}/presence/history?direction=backwards&limit=100"). to_return(:body => serialized_encoded_message_with_invalid_encoding, :headers => { 'Content-Type' => content_type }) } let(:presence_message) { presence.history.items.first } diff --git a/spec/acceptance/rest/push_admin_spec.rb b/spec/acceptance/rest/push_admin_spec.rb index a5e1433f0..36824fa40 100644 --- a/spec/acceptance/rest/push_admin_spec.rb +++ b/spec/acceptance/rest/push_admin_spec.rb @@ -90,7 +90,7 @@ end let!(:publish_stub) do - stub_request(:post, "#{client.endpoint}/push/publish"). + stub_request(:post, "#{client.uri}/push/publish"). with do |request| expect(deserialize_body(request.body, protocol)['recipient']['camelCase']['secondLevelCamelCase']).to eql('val') expect(deserialize_body(request.body, protocol)['recipient']).to_not have_key('camel_case') @@ -119,7 +119,7 @@ 'transportType' => 'ablyChannel', 'channel' => channel, 'ablyKey' => api_key, - 'ablyUrl' => client.endpoint.to_s + 'ablyUrl' => client.uri.to_s } end let(:notification_payload) do diff --git a/spec/shared/client_initializer_behaviour.rb b/spec/shared/client_initializer_behaviour.rb index 84ff2df4c..9d0d66a81 100644 --- a/spec/shared/client_initializer_behaviour.rb +++ b/spec/shared/client_initializer_behaviour.rb @@ -1,14 +1,6 @@ # encoding: utf-8 shared_examples 'a client initializer' do - def subdomain - if rest? - 'rest' - else - 'realtime' - end - end - def protocol if rest? 'http' @@ -154,38 +146,38 @@ def rest? end end - context 'endpoint' do + context 'uri' do before do allow_any_instance_of(subject.class).to receive(:auto_connect).and_return(false) end it 'defaults to production' do - expect(subject.endpoint.to_s).to eql("#{protocol}s://#{subdomain}.ably.io") + expect(subject.uri.to_s).to eql("#{protocol}s://main.realtime.ably.net") end context 'with environment option' do let(:client_options) { default_options.merge(environment: 'sandbox', auto_connect: false) } - it 'uses an alternate endpoint' do - expect(subject.endpoint.to_s).to eql("#{protocol}s://sandbox-#{subdomain}.ably.io") + it 'uses an alternate uri' do + expect(subject.uri.to_s).to eql("#{protocol}s://sandbox.realtime.ably.net") end end context 'with rest_host option' do let(:client_options) { default_options.merge(rest_host: 'custom-rest.host.com', auto_connect: false) } - it 'uses an alternate endpoint for REST clients' do + it 'uses an alternate uri for REST clients' do skip 'does not apply as testing a Realtime client' unless rest? - expect(subject.endpoint.to_s).to eql("#{protocol}s://custom-rest.host.com") + expect(subject.uri.to_s).to eql("#{protocol}s://custom-rest.host.com") end end context 'with realtime_host option' do let(:client_options) { default_options.merge(realtime_host: 'custom-realtime.host.com', auto_connect: false) } - it 'uses an alternate endpoint for Realtime clients' do + it 'uses an alternate uri for Realtime clients' do skip 'does not apply as testing a REST client' if rest? - expect(subject.endpoint.to_s).to eql("#{protocol}s://custom-realtime.host.com") + expect(subject.uri.to_s).to eql("#{protocol}s://custom-realtime.host.com") end end @@ -193,7 +185,7 @@ def rest? let(:client_options) { default_options.merge(port: 999, tls: false, auto_connect: false) } it 'uses the custom port for non-TLS requests' do - expect(subject.endpoint.to_s).to include(":999") + expect(subject.uri.to_s).to include(":999") end end @@ -201,7 +193,7 @@ def rest? let(:client_options) { default_options.merge(tls_port: 666, tls: true, auto_connect: false) } it 'uses the custom port for TLS requests' do - expect(subject.endpoint.to_s).to include(":666") + expect(subject.uri.to_s).to include(":666") end end end @@ -219,7 +211,7 @@ def rest? end it 'uses HTTP' do - expect(subject.endpoint.to_s).to eql("#{protocol}://#{subdomain}.ably.io") + expect(subject.uri.to_s).to eql("#{protocol}://main.realtime.ably.net") end end @@ -270,7 +262,7 @@ def rest? context 'when set without custom fallback hosts configured' do let(:environment) { 'foo' } let(:client_options) { default_options.merge(environment: environment) } - let(:default_fallbacks) { %w(a b c d e).map { |id| "#{environment}-#{id}-fallback.ably-realtime.com" } } + let(:default_fallbacks) { %w(a b c d e).map { |id| "#{environment}.#{id}.fallback.ably-realtime.com" } } it 'sets the environment attribute' do expect(subject.environment).to eql(environment) @@ -298,7 +290,7 @@ def rest? context 'when set with fallback_hosts_use_default' do let(:environment) { 'foo' } let(:custom_fallbacks) { %w(a b c).map { |id| "#{environment}-#{id}.foo.com" } } - let(:default_production_fallbacks) { %w(a b c d e).map { |id| "#{id}.ably-realtime.com" } } + let(:default_production_fallbacks) { %w(a b c d e).map { |id| "main.#{id}.fallback.ably-realtime.com" } } let(:client_options) { default_options.merge(environment: environment, fallback_hosts_use_default: true) } it 'sets the environment attribute' do diff --git a/spec/support/test_app.rb b/spec/support/test_app.rb index 2e7fc3944..e1bf93ae1 100644 --- a/spec/support/test_app.rb +++ b/spec/support/test_app.rb @@ -57,7 +57,7 @@ def restricted_api_key def delete return unless TestApp.instance_variable_get('@singleton__instance__') - url = "#{sandbox_client.endpoint}/apps/#{app_id}" + url = "#{sandbox_client.uri}/apps/#{app_id}" basic_auth = Base64.urlsafe_encode64(api_key).chomp headers = { "Authorization" => "Basic #{basic_auth}" } @@ -66,11 +66,11 @@ def delete end def environment - ENV['ABLY_ENV'] || 'sandbox' + ENV['ABLY_ENV'] || 'nonprod:sandbox' end def create_test_app - url = "#{sandbox_client.endpoint}/apps" + url = "#{sandbox_client.uri}/apps" headers = { 'Accept' => 'application/json', @@ -86,7 +86,7 @@ def create_test_app end def host - sandbox_client.endpoint.host + sandbox_client.uri.host end def realtime_host @@ -94,12 +94,13 @@ def realtime_host end def create_test_stats(stats) - client = Ably::Rest::Client.new(key: api_key, environment: environment) + client = Ably::Rest::Client.new(key: api_key, endpoint: environment) response = client.post('/stats', stats) raise "Could not create stats fixtures. Ably responded with status #{response.status}\n#{response.body}" unless (200..299).include?(response.status) end private + def sandbox_client @sandbox_client ||= Ably::Rest::Client.new(key: 'app.key:secret', tls: true, environment: environment) end diff --git a/spec/unit/models/http_paginated_result_spec.rb b/spec/unit/models/http_paginated_result_spec.rb index 1c4ac1990..cb8ef1bc8 100644 --- a/spec/unit/models/http_paginated_result_spec.rb +++ b/spec/unit/models/http_paginated_result_spec.rb @@ -23,7 +23,7 @@ status: status }) end - let(:base_url) { 'http://rest.ably.io/channels/channel_name' } + let(:base_url) { 'http://main.realtime.ably.net/channels/channel_name' } let(:full_url) { "#{base_url}/whatever?param=exists" } let(:paginated_result_options) { Hash.new } let(:first_paged_request) { paginated_result_class.new(http_response, full_url, client, paginated_result_options) } @@ -193,7 +193,7 @@ end context 'with paged http response' do - let(:base_url) { 'http://rest.ably.io/channels/channel_name' } + let(:base_url) { 'http://main.realtime.ably.net/channels/channel_name' } let(:full_url) { "#{base_url}/messages" } let(:headers) do { diff --git a/spec/unit/realtime/connection_spec.rb b/spec/unit/realtime/connection_spec.rb index 9c227af0a..08e667339 100644 --- a/spec/unit/realtime/connection_spec.rb +++ b/spec/unit/realtime/connection_spec.rb @@ -2,7 +2,7 @@ require 'shared/protocol_msgbus_behaviour' describe Ably::Realtime::Connection do - let(:client) { instance_double('Ably::Realtime::Client', logger: double('logger').as_null_object, recover: nil, endpoint: double('endpoint', host: 'realtime.ably.io')) } + let(:client) { instance_double('Ably::Realtime::Client', logger: double('logger').as_null_object, recover: nil, uri: double('uri', host: 'main.realtime.ably.net')) } subject do Ably::Realtime::Connection.new(client, {}).tap do |connection|