From 842bd775182557baf94b227e5d7e7740828d8425 Mon Sep 17 00:00:00 2001 From: Laura Martin Date: Tue, 31 Dec 2024 10:09:54 +0000 Subject: [PATCH 01/12] 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 --- ably/export_test.go | 12 + ably/options.go | 109 +++++++- ably/options_test.go | 244 ++++++++++++----- ably/realtime_client_integration_test.go | 323 ++++++++++++++++------- ably/realtime_conn.go | 1 + ablytest/ablytest.go | 1 + 6 files changed, 513 insertions(+), 177 deletions(-) diff --git a/ably/export_test.go b/ably/export_test.go index c23640d7f..f936e0b08 100644 --- a/ably/export_test.go +++ b/ably/export_test.go @@ -16,6 +16,14 @@ func GetEnvFallbackHosts(env string) []string { return getEnvFallbackHosts(env) } +func GetEndpointFallbackHosts(endpoint string) []string { + return getEndpointFallbackHosts(endpoint) +} + +func (opts *clientOptions) GetEndpoint() string { + return opts.getEndpoint() +} + func (opts *clientOptions) GetRestHost() string { return opts.getRestHost() } @@ -192,6 +200,10 @@ func ApplyOptionsWithDefaults(o ...ClientOption) *clientOptions { return applyOptionsWithDefaults(o...) } +func EndpointFqdn(endpoint string) bool { + return endpointFqdn(endpoint) +} + type ConnStateChanges = connStateChanges type ChannelStateChanges = channelStateChanges diff --git a/ably/options.go b/ably/options.go index 6c7c8362e..bd5c7bace 100644 --- a/ably/options.go +++ b/ably/options.go @@ -12,6 +12,7 @@ import ( "net/http/httptrace" "net/url" "os" + "regexp" "strconv" "strings" "time" @@ -23,6 +24,9 @@ const ( protocolJSON = "application/json" protocolMsgPack = "application/x-msgpack" + // endpoint is the default routing policy used to connect to Ably + endpoint = "main" + // restHost is the primary ably host. restHost = "rest.ably.io" // realtimeHost is the primary ably host. @@ -37,6 +41,7 @@ const ( ) var defaultOptions = clientOptions{ + Endpoint: endpoint, RESTHost: restHost, FallbackHosts: defaultFallbackHosts(), HTTPMaxRetryCount: 3, @@ -59,13 +64,24 @@ var defaultOptions = clientOptions{ } func defaultFallbackHosts() []string { - return []string{ - "a.ably-realtime.com", - "b.ably-realtime.com", - "c.ably-realtime.com", - "d.ably-realtime.com", - "e.ably-realtime.com", + return endpointFallbacks("main", "ably-realtime.com") +} + +func getEndpointFallbackHosts(endpoint string) []string { + if match := regexp.MustCompile(`^nonprod:(.*)$`).FindStringSubmatch(endpoint); match != nil { + namespace := match[1] + return endpointFallbacks(namespace, "ably-realtime-nonprod.com") } + + return endpointFallbacks(endpoint, "ably-realtime.com") +} + +func endpointFallbacks(namespace, root string) []string { + var fallbacks []string + for _, id := range []string{"a", "b", "c", "d", "e"} { + fallbacks = append(fallbacks, fmt.Sprintf("%s.%s.fallback.%s", namespace, id, root)) + } + return fallbacks } func getEnvFallbackHosts(env string) []string { @@ -244,8 +260,17 @@ type clientOptions struct { // authOptions Embedded an [ably.authOptions] object (TO3j). authOptions - // RESTHost enables a non-default Ably host to be specified. For development environments only. - // The default value is rest.ably.io (RSC12, TO3k2). + // Endpoint specifies the domain used to connect to Ably. It has the following properties: + // If the endpoint option is a production routing policy name of the form [name] then the primary domain is [name].realtime.ably.net (REC1b4). + // If the endpoint option is a non-production routing policy name of the form nonprod:[name] then the primary domain is [name].realtime.ably-nonprod.net (REC1b3). + // If the endpoint option is a domain name, determined by it containing at least one period (.) then the primary domain is the value of the endpoint option (REC1b2). + // If any one of the deprecated options environment, restHost, realtimeHost are also specified then the options as a set are invalid (REC1b1). + Endpoint string + + // Deprecated: this property is deprecated and will be removed in a future version. + // RESTHost enables a non-default Ably host to be specified. For + // development environments only. The default value is rest.ably.io (RSC12, + // TO3k2). RESTHost string // Deprecated: this property is deprecated and will be removed in a future version. @@ -257,10 +282,12 @@ type clientOptions struct { // please specify them here (RSC15b, RSC15a, TO3k6). FallbackHosts []string + // Deprecated: this property is deprecated and will be removed in a future version. // RealtimeHost enables a non-default Ably host to be specified for realtime connections. // For development environments only. The default value is realtime.ably.io (RTC1d, TO3k3). RealtimeHost string + // Deprecated: this property is deprecated and will be removed in a future version. // Environment enables a custom environment to be used with the Ably service. // Optional: prefixes both hostname with the environment string (RSC15b, TO3k1). Environment string @@ -415,6 +442,13 @@ type clientOptions struct { } func (opts *clientOptions) validate() error { + if !empty(opts.Endpoint) && (!empty(opts.Environment) || !empty(opts.RealtimeHost) || !empty(opts.RESTHost)) { + err := errors.New("invalid client option: cannot use endpoint with any of environment, realtimeHost or restHost") + logger := opts.LogHandler + logger.Printf(LogError, "Invalid client options : %v", err.Error()) + return err + } + _, err := opts.getFallbackHosts() if err != nil { logger := opts.LogHandler @@ -450,7 +484,39 @@ func (opts *clientOptions) activePort() (port int, isDefault bool) { return } +func (opts *clientOptions) usingLegacyOpts() bool { + return empty(opts.Endpoint) && (!empty(opts.Environment) || !empty(opts.RESTHost) || !empty(opts.RealtimeHost)) +} + +// endpointFqdn handles an endpoint that uses a hostname, which may be an IPv4 +// address, IPv6 address or localhost +func endpointFqdn(endpoint string) bool { + return strings.Contains(endpoint, ".") || strings.Contains(endpoint, "::") || endpoint == "localhost" +} + +func (opts *clientOptions) getEndpoint() string { + endpoint := opts.Endpoint + if empty(endpoint) { + endpoint = defaultOptions.Endpoint + } + + if endpointFqdn(opts.Endpoint) { + return endpoint + } + + if match := regexp.MustCompile(`^nonprod:(.*)$`).FindStringSubmatch(endpoint); match != nil { + namespace := match[1] + return fmt.Sprintf("%s.realtime.ably-nonprod.net", namespace) + } + + return fmt.Sprintf("%s.realtime.ably.net", endpoint) +} + func (opts *clientOptions) getRestHost() string { + if !opts.usingLegacyOpts() { + return opts.getEndpoint() + } + if !empty(opts.RESTHost) { return opts.RESTHost } @@ -461,6 +527,10 @@ func (opts *clientOptions) getRestHost() string { } func (opts *clientOptions) getRealtimeHost() string { + if !opts.usingLegacyOpts() { + return opts.getEndpoint() + } + if !empty(opts.RealtimeHost) { return opts.RealtimeHost } @@ -508,6 +578,7 @@ func (opts *clientOptions) realtimeURL(realtimeHost string) (realtimeUrl string) func (opts *clientOptions) getFallbackHosts() ([]string, error) { logger := opts.LogHandler _, isDefaultPort := opts.activePort() + if opts.FallbackHostsUseDefault { if opts.FallbackHosts != nil { return nil, errors.New("fallbackHosts and fallbackHostsUseDefault cannot both be set") @@ -521,12 +592,21 @@ func (opts *clientOptions) getFallbackHosts() ([]string, error) { logger.Printf(LogWarning, "Deprecated fallbackHostsUseDefault : using default fallbackhosts") return defaultOptions.FallbackHosts, nil } + + if opts.FallbackHosts == nil && !empty(opts.Endpoint) { + if endpointFqdn(opts.Endpoint) { + return opts.FallbackHosts, nil + } + return getEndpointFallbackHosts(opts.Endpoint), nil + } + if opts.FallbackHosts == nil && empty(opts.RESTHost) && empty(opts.RealtimeHost) && isDefaultPort { if opts.isProductionEnvironment() { return defaultOptions.FallbackHosts, nil } return getEnvFallbackHosts(opts.Environment), nil } + return opts.FallbackHosts, nil } @@ -1070,6 +1150,18 @@ func WithEchoMessages(echo bool) ClientOption { } } +// WithEndpoint is used for setting Endpoint using [ably.ClientOption]. +// Endpoint specifies the domain used to connect to Ably. It has the following properties: +// If the endpoint option is a production routing policy name of the form [name] then the primary domain is [name].realtime.ably.net (REC1b4). +// If the endpoint option is a non-production routing policy name of the form nonprod:[name] then the primary domain is [name].realtime.ably-nonprod.net (REC1b3). +// If the endpoint option is a domain name, determined by it containing at least one period (.) then the primary domain is the value of the endpoint option (REC1b2). +// If any one of the deprecated options environment, restHost, realtimeHost are also specified then the options as a set are invalid (REC1b1). +func WithEndpoint(env string) ClientOption { + return func(os *clientOptions) { + os.Endpoint = env + } +} + // WithEnvironment is used for setting Environment using [ably.ClientOption]. // Environment enables a custom environment to be used with the Ably service. // Optional: prefixes both hostname with the environment string (RSC15b, TO3k1). @@ -1331,6 +1423,7 @@ func WithInsecureAllowBasicAuthWithoutTLS() ClientOption { func applyOptionsWithDefaults(opts ...ClientOption) *clientOptions { to := defaultOptions // No need to set hosts by default + to.Endpoint = "" to.RESTHost = "" to.RealtimeHost = "" to.FallbackHosts = nil diff --git a/ably/options_test.go b/ably/options_test.go index cc86e7942..af5be52f6 100644 --- a/ably/options_test.go +++ b/ably/options_test.go @@ -15,16 +15,42 @@ import ( func TestDefaultFallbacks_RSC15h(t *testing.T) { expectedFallBackHosts := []string{ - "a.ably-realtime.com", - "b.ably-realtime.com", - "c.ably-realtime.com", - "d.ably-realtime.com", - "e.ably-realtime.com", + "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", } hosts := ably.DefaultFallbackHosts() assert.Equal(t, expectedFallBackHosts, hosts) } +func TestEndpointFallbacks(t *testing.T) { + t.Run("standard endpoint", func(t *testing.T) { + expectedFallBackHosts := []string{ + "acme.a.fallback.ably-realtime.com", + "acme.b.fallback.ably-realtime.com", + "acme.c.fallback.ably-realtime.com", + "acme.d.fallback.ably-realtime.com", + "acme.e.fallback.ably-realtime.com", + } + hosts := ably.GetEndpointFallbackHosts("acme") + assert.Equal(t, expectedFallBackHosts, hosts) + }) + + t.Run("nonprod endpoint", func(t *testing.T) { + expectedFallBackHosts := []string{ + "acme.a.fallback.ably-realtime-nonprod.com", + "acme.b.fallback.ably-realtime-nonprod.com", + "acme.c.fallback.ably-realtime-nonprod.com", + "acme.d.fallback.ably-realtime-nonprod.com", + "acme.e.fallback.ably-realtime-nonprod.com", + } + hosts := ably.GetEndpointFallbackHosts("nonprod:acme") + assert.Equal(t, expectedFallBackHosts, hosts) + }) +} + func TestEnvFallbackHosts_RSC15i(t *testing.T) { expectedFallBackHosts := []string{ "sandbox-a-fallback.ably-realtime.com", @@ -42,11 +68,11 @@ func TestInternetConnectionCheck_RTN17c(t *testing.T) { assert.True(t, clientOptions.HasActiveInternetConnection()) } -func TestFallbackHosts_RSC15b(t *testing.T) { - t.Run("RSC15e RSC15g3 with default options", func(t *testing.T) { +func TestHosts_RSC15b(t *testing.T) { + t.Run("with default options", func(t *testing.T) { clientOptions := ably.NewClientOptions() - assert.Equal(t, "realtime.ably.io", clientOptions.GetRealtimeHost()) - assert.Equal(t, "rest.ably.io", clientOptions.GetRestHost()) + assert.Equal(t, "main.realtime.ably.net", clientOptions.GetRealtimeHost()) + assert.Equal(t, "main.realtime.ably.net", clientOptions.GetRestHost()) assert.False(t, clientOptions.NoTLS) port, isDefaultPort := clientOptions.ActivePort() assert.Equal(t, 443, port) @@ -55,101 +81,153 @@ func TestFallbackHosts_RSC15b(t *testing.T) { assert.Equal(t, ably.DefaultFallbackHosts(), fallbackHosts) }) - t.Run("RSC15h with production environment", func(t *testing.T) { - clientOptions := ably.NewClientOptions(ably.WithEnvironment("production")) - assert.Equal(t, "realtime.ably.io", clientOptions.GetRealtimeHost()) - assert.Equal(t, "rest.ably.io", clientOptions.GetRestHost()) + t.Run("with endpoint as a custom routing policy name", func(t *testing.T) { + clientOptions := ably.NewClientOptions(ably.WithEndpoint("acme")) + assert.Equal(t, "acme.realtime.ably.net", clientOptions.GetRestHost()) + assert.Equal(t, "acme.realtime.ably.net", clientOptions.GetRealtimeHost()) assert.False(t, clientOptions.NoTLS) port, isDefaultPort := clientOptions.ActivePort() assert.Equal(t, 443, port) assert.True(t, isDefaultPort) fallbackHosts, _ := clientOptions.GetFallbackHosts() - assert.Equal(t, ably.DefaultFallbackHosts(), fallbackHosts) + assert.Equal(t, ably.GetEndpointFallbackHosts("acme"), fallbackHosts) }) - t.Run("RSC15g2 RTC1e with custom environment", func(t *testing.T) { - clientOptions := ably.NewClientOptions(ably.WithEnvironment("sandbox")) - assert.Equal(t, "sandbox-realtime.ably.io", clientOptions.GetRealtimeHost()) - assert.Equal(t, "sandbox-rest.ably.io", clientOptions.GetRestHost()) + t.Run("with endpoint as a nonprod routing policy name", func(t *testing.T) { + clientOptions := ably.NewClientOptions(ably.WithEndpoint("nonprod:acme")) + assert.Equal(t, "acme.realtime.ably-nonprod.net", clientOptions.GetRestHost()) + assert.Equal(t, "acme.realtime.ably-nonprod.net", clientOptions.GetRealtimeHost()) assert.False(t, clientOptions.NoTLS) port, isDefaultPort := clientOptions.ActivePort() assert.Equal(t, 443, port) assert.True(t, isDefaultPort) fallbackHosts, _ := clientOptions.GetFallbackHosts() - assert.Equal(t, ably.GetEnvFallbackHosts("sandbox"), fallbackHosts) - }) - - t.Run("RSC15g4 RTC1e with custom environment and fallbackHostUseDefault", func(t *testing.T) { - clientOptions := ably.NewClientOptions(ably.WithEnvironment("sandbox"), ably.WithFallbackHostsUseDefault(true)) - assert.Equal(t, "sandbox-realtime.ably.io", clientOptions.GetRealtimeHost()) - assert.Equal(t, "sandbox-rest.ably.io", clientOptions.GetRestHost()) - assert.False(t, clientOptions.NoTLS) - port, isDefaultPort := clientOptions.ActivePort() - assert.Equal(t, 443, port) - assert.True(t, isDefaultPort) - fallbackHosts, _ := clientOptions.GetFallbackHosts() - assert.Equal(t, ably.DefaultFallbackHosts(), fallbackHosts) - }) - - t.Run("RSC11b RTN17b RTC1e with custom environment and non default ports", func(t *testing.T) { - clientOptions := ably.NewClientOptions( - ably.WithEnvironment("local"), - ably.WithPort(8080), - ably.WithTLSPort(8081), - ) - assert.Equal(t, "local-realtime.ably.io", clientOptions.GetRealtimeHost()) - assert.Equal(t, "local-rest.ably.io", clientOptions.GetRestHost()) - assert.False(t, clientOptions.NoTLS) - port, isDefaultPort := clientOptions.ActivePort() - assert.Equal(t, 8081, port) - assert.False(t, isDefaultPort) - fallbackHosts, _ := clientOptions.GetFallbackHosts() - assert.Nil(t, fallbackHosts) + assert.Equal(t, ably.GetEndpointFallbackHosts("nonprod:acme"), fallbackHosts) }) - t.Run("RSC11 with custom rest host", func(t *testing.T) { - clientOptions := ably.NewClientOptions(ably.WithRESTHost("test.org")) - assert.Equal(t, "test.org", clientOptions.GetRealtimeHost()) - assert.Equal(t, "test.org", clientOptions.GetRestHost()) + t.Run("with endpoint as a fqdn with no fallbackHosts specified", func(t *testing.T) { + clientOptions := ably.NewClientOptions(ably.WithEndpoint("foo.example.com")) + assert.Equal(t, "foo.example.com", clientOptions.GetRestHost()) + assert.Equal(t, "foo.example.com", clientOptions.GetRealtimeHost()) assert.False(t, clientOptions.NoTLS) port, isDefaultPort := clientOptions.ActivePort() assert.Equal(t, 443, port) assert.True(t, isDefaultPort) - fallbackHosts, _ := clientOptions.GetFallbackHosts() + fallbackHosts, err := clientOptions.GetFallbackHosts() + assert.NoError(t, err) assert.Nil(t, fallbackHosts) }) - t.Run("RSC11 with custom rest host and realtime host", func(t *testing.T) { - clientOptions := ably.NewClientOptions(ably.WithRealtimeHost("ws.test.org"), ably.WithRESTHost("test.org")) - assert.Equal(t, "ws.test.org", clientOptions.GetRealtimeHost()) - assert.Equal(t, "test.org", clientOptions.GetRestHost()) + t.Run("with endpoint as a fqdn with fallbackHosts specified", func(t *testing.T) { + clientOptions := ably.NewClientOptions(ably.WithEndpoint("foo.example.com"), ably.WithFallbackHosts([]string{"fallback.foo.example.com"})) + assert.Equal(t, "foo.example.com", clientOptions.GetRestHost()) + assert.Equal(t, "foo.example.com", clientOptions.GetRealtimeHost()) assert.False(t, clientOptions.NoTLS) port, isDefaultPort := clientOptions.ActivePort() assert.Equal(t, 443, port) assert.True(t, isDefaultPort) - fallbackHosts, _ := clientOptions.GetFallbackHosts() - assert.Nil(t, fallbackHosts) + fallbackHosts, err := clientOptions.GetFallbackHosts() + assert.NoError(t, err) + assert.Equal(t, []string{"fallback.foo.example.com"}, fallbackHosts) }) - t.Run("RSC15b with custom rest host and realtime host and fallbackHostsUseDefault", func(t *testing.T) { - clientOptions := ably.NewClientOptions( - ably.WithRealtimeHost("ws.test.org"), - ably.WithRESTHost("test.org"), - ably.WithFallbackHostsUseDefault(true)) - assert.Equal(t, "ws.test.org", clientOptions.GetRealtimeHost()) - assert.Equal(t, "test.org", clientOptions.GetRestHost()) - assert.False(t, clientOptions.NoTLS) - port, isDefaultPort := clientOptions.ActivePort() - assert.Equal(t, 443, port) - assert.True(t, isDefaultPort) - fallbackHosts, _ := clientOptions.GetFallbackHosts() - assert.Equal(t, ably.DefaultFallbackHosts(), fallbackHosts) + t.Run("legacy support", func(t *testing.T) { + t.Run("RSC15h with production environment", func(t *testing.T) { + clientOptions := ably.NewClientOptions(ably.WithEnvironment("production")) + assert.Equal(t, "realtime.ably.io", clientOptions.GetRealtimeHost()) + assert.Equal(t, "rest.ably.io", clientOptions.GetRestHost()) + assert.False(t, clientOptions.NoTLS) + port, isDefaultPort := clientOptions.ActivePort() + assert.Equal(t, 443, port) + assert.True(t, isDefaultPort) + fallbackHosts, _ := clientOptions.GetFallbackHosts() + assert.Equal(t, ably.DefaultFallbackHosts(), fallbackHosts) + }) + + t.Run("RSC15g2 RTC1e with custom environment", func(t *testing.T) { + clientOptions := ably.NewClientOptions(ably.WithEnvironment("sandbox")) + assert.Equal(t, "sandbox-realtime.ably.io", clientOptions.GetRealtimeHost()) + assert.Equal(t, "sandbox-rest.ably.io", clientOptions.GetRestHost()) + assert.False(t, clientOptions.NoTLS) + port, isDefaultPort := clientOptions.ActivePort() + assert.Equal(t, 443, port) + assert.True(t, isDefaultPort) + fallbackHosts, _ := clientOptions.GetFallbackHosts() + assert.Equal(t, ably.GetEnvFallbackHosts("sandbox"), fallbackHosts) + }) + + t.Run("RSC15g4 RTC1e with custom environment and fallbackHostUseDefault", func(t *testing.T) { + clientOptions := ably.NewClientOptions(ably.WithEnvironment("sandbox"), ably.WithFallbackHostsUseDefault(true)) + assert.Equal(t, "sandbox-realtime.ably.io", clientOptions.GetRealtimeHost()) + assert.Equal(t, "sandbox-rest.ably.io", clientOptions.GetRestHost()) + assert.False(t, clientOptions.NoTLS) + port, isDefaultPort := clientOptions.ActivePort() + assert.Equal(t, 443, port) + assert.True(t, isDefaultPort) + fallbackHosts, _ := clientOptions.GetFallbackHosts() + assert.Equal(t, ably.DefaultFallbackHosts(), fallbackHosts) + }) + + t.Run("RSC11b RTN17b RTC1e with legacy custom environment and non default ports", func(t *testing.T) { + clientOptions := ably.NewClientOptions( + ably.WithEnvironment("local"), + ably.WithPort(8080), + ably.WithTLSPort(8081), + ) + assert.Equal(t, "local-realtime.ably.io", clientOptions.GetRealtimeHost()) + assert.Equal(t, "local-rest.ably.io", clientOptions.GetRestHost()) + assert.False(t, clientOptions.NoTLS) + port, isDefaultPort := clientOptions.ActivePort() + assert.Equal(t, 8081, port) + assert.False(t, isDefaultPort) + fallbackHosts, _ := clientOptions.GetFallbackHosts() + assert.Nil(t, fallbackHosts) + }) + + t.Run("RSC11 with custom rest host", func(t *testing.T) { + clientOptions := ably.NewClientOptions(ably.WithRESTHost("test.org")) + assert.Equal(t, "test.org", clientOptions.GetRealtimeHost()) + assert.Equal(t, "test.org", clientOptions.GetRestHost()) + assert.False(t, clientOptions.NoTLS) + port, isDefaultPort := clientOptions.ActivePort() + assert.Equal(t, 443, port) + assert.True(t, isDefaultPort) + fallbackHosts, _ := clientOptions.GetFallbackHosts() + assert.Nil(t, fallbackHosts) + }) + + t.Run("RSC11 with custom rest host and realtime host", func(t *testing.T) { + clientOptions := ably.NewClientOptions(ably.WithRealtimeHost("ws.test.org"), ably.WithRESTHost("test.org")) + assert.Equal(t, "ws.test.org", clientOptions.GetRealtimeHost()) + assert.Equal(t, "test.org", clientOptions.GetRestHost()) + assert.False(t, clientOptions.NoTLS) + port, isDefaultPort := clientOptions.ActivePort() + assert.Equal(t, 443, port) + assert.True(t, isDefaultPort) + fallbackHosts, _ := clientOptions.GetFallbackHosts() + assert.Nil(t, fallbackHosts) + }) + + t.Run("RSC15b with custom rest host and realtime host and fallbackHostsUseDefault", func(t *testing.T) { + clientOptions := ably.NewClientOptions( + ably.WithRealtimeHost("ws.test.org"), + ably.WithRESTHost("test.org"), + ably.WithFallbackHostsUseDefault(true)) + assert.Equal(t, "ws.test.org", clientOptions.GetRealtimeHost()) + assert.Equal(t, "test.org", clientOptions.GetRestHost()) + assert.False(t, clientOptions.NoTLS) + port, isDefaultPort := clientOptions.ActivePort() + assert.Equal(t, 443, port) + assert.True(t, isDefaultPort) + fallbackHosts, _ := clientOptions.GetFallbackHosts() + assert.Equal(t, ably.DefaultFallbackHosts(), fallbackHosts) + }) }) t.Run("RSC15g1 with fallbackHosts", func(t *testing.T) { clientOptions := ably.NewClientOptions(ably.WithFallbackHosts([]string{"a.example.com", "b.example.com"})) - assert.Equal(t, "realtime.ably.io", clientOptions.GetRealtimeHost()) - assert.Equal(t, "rest.ably.io", clientOptions.GetRestHost()) + assert.Equal(t, "main.realtime.ably.net", clientOptions.GetRealtimeHost()) + assert.Equal(t, "main.realtime.ably.net", clientOptions.GetRestHost()) assert.False(t, clientOptions.NoTLS) port, isDefaultPort := clientOptions.ActivePort() assert.Equal(t, 443, port) @@ -204,6 +282,19 @@ func TestClientOptions(t *testing.T) { assert.Error(t, err, "expected an error") }) + t.Run("must return error on invalid combinations", func(t *testing.T) { + _, err := ably.NewREST([]ably.ClientOption{ably.WithEndpoint("acme"), ably.WithEnvironment("acme")}...) + assert.Error(t, err, + "expected an error") + + _, err = ably.NewREST([]ably.ClientOption{ably.WithEndpoint("acme"), ably.WithRealtimeHost("foo.example.com")}...) + assert.Error(t, err, + "expected an error") + + _, err = ably.NewREST([]ably.ClientOption{ably.WithEndpoint("acme"), ably.WithRESTHost("foo.example.com")}...) + assert.Error(t, err, + "expected an error") + }) } func TestScopeParams(t *testing.T) { @@ -328,3 +419,10 @@ func TestPaginateParams(t *testing.T) { "expected 100 got %s", values.Get("limit")) }) } + +func TestEndpointFqdn(t *testing.T) { + assert.Equal(t, false, ably.EndpointFqdn("sandbox")) + assert.Equal(t, true, ably.EndpointFqdn("sandbox.example.com")) + assert.Equal(t, true, ably.EndpointFqdn("127.0.0.1")) + assert.Equal(t, true, ably.EndpointFqdn("localhost")) +} diff --git a/ably/realtime_client_integration_test.go b/ably/realtime_client_integration_test.go index 857a022e5..0f69709bd 100644 --- a/ably/realtime_client_integration_test.go +++ b/ably/realtime_client_integration_test.go @@ -29,109 +29,218 @@ func TestRealtime_RealtimeHost(t *testing.T) { "localhost", "::1", } - for _, host := range hosts { - dial := make(chan string, 1) - client, err := ably.NewRealtime( - ably.WithKey("xxx:xxx"), - ably.WithRealtimeHost(host), - ably.WithAutoConnect(false), - ably.WithDial(func(protocol string, u *url.URL, timeout time.Duration) (ably.Conn, error) { - dial <- u.Host - return MessagePipe(nil, nil)(protocol, u, timeout) - }), - ) - assert.NoError(t, err) - client.Connect() - var recordedHost string - ablytest.Instantly.Recv(t, &recordedHost, dial, t.Fatalf) - h, _, err := net.SplitHostPort(recordedHost) - assert.NoError(t, err) - assert.Equal(t, host, h, "expected %q got %q", host, h) - } + + t.Run("with endpoint option", func(t *testing.T) { + for _, host := range hosts { + dial := make(chan string, 1) + client, err := ably.NewRealtime( + ably.WithKey("xxx:xxx"), + ably.WithEndpoint(host), + ably.WithAutoConnect(false), + ably.WithDial(func(protocol string, u *url.URL, timeout time.Duration) (ably.Conn, error) { + dial <- u.Host + return MessagePipe(nil, nil)(protocol, u, timeout) + }), + ) + assert.NoError(t, err) + client.Connect() + var recordedHost string + ablytest.Instantly.Recv(t, &recordedHost, dial, t.Fatalf) + h, _, err := net.SplitHostPort(recordedHost) + assert.NoError(t, err) + assert.Equal(t, host, h, "expected %q got %q", host, h) + } + }) + + t.Run("with legacy realtimeHost option", func(t *testing.T) { + for _, host := range hosts { + dial := make(chan string, 1) + client, err := ably.NewRealtime( + ably.WithKey("xxx:xxx"), + ably.WithRealtimeHost(host), + ably.WithAutoConnect(false), + ably.WithDial(func(protocol string, u *url.URL, timeout time.Duration) (ably.Conn, error) { + dial <- u.Host + return MessagePipe(nil, nil)(protocol, u, timeout) + }), + ) + assert.NoError(t, err) + client.Connect() + var recordedHost string + ablytest.Instantly.Recv(t, &recordedHost, dial, t.Fatalf) + h, _, err := net.SplitHostPort(recordedHost) + assert.NoError(t, err) + assert.Equal(t, host, h, "expected %q got %q", host, h) + } + }) } func TestRealtime_RSC7_AblyAgent(t *testing.T) { - t.Run("RSC7d3 : Should set ablyAgent header with correct identifiers", func(t *testing.T) { - var agentHeaderValue string - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - agentHeaderValue = r.Header.Get(ably.AblyAgentHeader) - w.WriteHeader(http.StatusInternalServerError) - })) - defer server.Close() - serverURL, err := url.Parse(server.URL) - assert.NoError(t, err) - - client, err := ably.NewRealtime( - ably.WithEnvironment(ablytest.Environment), - ably.WithTLS(false), - ably.WithToken("fake:token"), - ably.WithUseTokenAuth(true), - ably.WithRealtimeHost(serverURL.Host)) - assert.NoError(t, err) - defer client.Close() + t.Run("using endpoint option", func(t *testing.T) { + t.Run("RSC7d3 : Should set ablyAgent header with correct identifiers", func(t *testing.T) { + var agentHeaderValue string + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + agentHeaderValue = r.Header.Get(ably.AblyAgentHeader) + w.WriteHeader(http.StatusInternalServerError) + })) + defer server.Close() + serverURL, err := url.Parse(server.URL) + assert.NoError(t, err) - expectedAgentHeaderValue := ably.AblySDKIdentifier + " " + ably.GoRuntimeIdentifier + " " + ably.GoOSIdentifier() - ablytest.Wait(ablytest.ConnWaiter(client, nil, ably.ConnectionEventDisconnected), nil) + client, err := ably.NewRealtime( + ably.WithEndpoint(serverURL.Host), + ably.WithTLS(false), + ably.WithToken("fake:token"), + ably.WithUseTokenAuth(true)) + assert.NoError(t, err) + defer client.Close() - assert.Equal(t, expectedAgentHeaderValue, agentHeaderValue) - }) + expectedAgentHeaderValue := ably.AblySDKIdentifier + " " + ably.GoRuntimeIdentifier + " " + ably.GoOSIdentifier() + ablytest.Wait(ablytest.ConnWaiter(client, nil, ably.ConnectionEventDisconnected), nil) - t.Run("RSC7d6 : Should set ablyAgent header with custom agents", func(t *testing.T) { - var agentHeaderValue string - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - agentHeaderValue = r.Header.Get(ably.AblyAgentHeader) - w.WriteHeader(http.StatusInternalServerError) - })) - defer server.Close() - serverURL, err := url.Parse(server.URL) - assert.NoError(t, err) - - client, err := ably.NewRealtime( - ably.WithEnvironment(ablytest.Environment), - ably.WithTLS(false), - ably.WithToken("fake:token"), - ably.WithUseTokenAuth(true), - ably.WithRealtimeHost(serverURL.Host), - ably.WithAgents(map[string]string{ - "foo": "1.2.3", - }), - ) - assert.NoError(t, err) - defer client.Close() + assert.Equal(t, expectedAgentHeaderValue, agentHeaderValue) + }) + + t.Run("RSC7d6 : Should set ablyAgent header with custom agents", func(t *testing.T) { + var agentHeaderValue string + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + agentHeaderValue = r.Header.Get(ably.AblyAgentHeader) + w.WriteHeader(http.StatusInternalServerError) + })) + defer server.Close() + serverURL, err := url.Parse(server.URL) + assert.NoError(t, err) + + client, err := ably.NewRealtime( + ably.WithEndpoint(serverURL.Host), + ably.WithTLS(false), + ably.WithToken("fake:token"), + ably.WithUseTokenAuth(true), + ably.WithAgents(map[string]string{ + "foo": "1.2.3", + }), + ) + assert.NoError(t, err) + defer client.Close() + + expectedAgentHeaderValue := ably.AblySDKIdentifier + " " + ably.GoRuntimeIdentifier + " " + ably.GoOSIdentifier() + " foo/1.2.3" + ablytest.Wait(ablytest.ConnWaiter(client, nil, ably.ConnectionEventDisconnected), nil) + + assert.Equal(t, expectedAgentHeaderValue, agentHeaderValue) + }) - expectedAgentHeaderValue := ably.AblySDKIdentifier + " " + ably.GoRuntimeIdentifier + " " + ably.GoOSIdentifier() + " foo/1.2.3" - ablytest.Wait(ablytest.ConnWaiter(client, nil, ably.ConnectionEventDisconnected), nil) + t.Run("RSC7d6 : Should set ablyAgent header with custom agents missing version", func(t *testing.T) { + var agentHeaderValue string + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + agentHeaderValue = r.Header.Get(ably.AblyAgentHeader) + w.WriteHeader(http.StatusInternalServerError) + })) + defer server.Close() + serverURL, err := url.Parse(server.URL) + assert.NoError(t, err) + + client, err := ably.NewRealtime( + ably.WithEndpoint(serverURL.Host), + ably.WithTLS(false), + ably.WithToken("fake:token"), + ably.WithUseTokenAuth(true), + ably.WithAgents(map[string]string{ + "bar": "", + }), + ) + assert.NoError(t, err) + defer client.Close() + + expectedAgentHeaderValue := ably.AblySDKIdentifier + " " + ably.GoRuntimeIdentifier + " " + ably.GoOSIdentifier() + " bar" + ablytest.Wait(ablytest.ConnWaiter(client, nil, ably.ConnectionEventDisconnected), nil) - assert.Equal(t, expectedAgentHeaderValue, agentHeaderValue) + assert.Equal(t, expectedAgentHeaderValue, agentHeaderValue) + }) }) - t.Run("RSC7d6 : Should set ablyAgent header with custom agents missing version", func(t *testing.T) { - var agentHeaderValue string - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - agentHeaderValue = r.Header.Get(ably.AblyAgentHeader) - w.WriteHeader(http.StatusInternalServerError) - })) - defer server.Close() - serverURL, err := url.Parse(server.URL) - assert.NoError(t, err) - - client, err := ably.NewRealtime( - ably.WithEnvironment(ablytest.Environment), - ably.WithTLS(false), - ably.WithToken("fake:token"), - ably.WithUseTokenAuth(true), - ably.WithRealtimeHost(serverURL.Host), - ably.WithAgents(map[string]string{ - "bar": "", - }), - ) - assert.NoError(t, err) - defer client.Close() + t.Run("using legacy options", func(t *testing.T) { + t.Run("RSC7d3 : Should set ablyAgent header with correct identifiers", func(t *testing.T) { + var agentHeaderValue string + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + agentHeaderValue = r.Header.Get(ably.AblyAgentHeader) + w.WriteHeader(http.StatusInternalServerError) + })) + defer server.Close() + serverURL, err := url.Parse(server.URL) + assert.NoError(t, err) + + client, err := ably.NewRealtime( + ably.WithEnvironment(ablytest.Environment), + ably.WithTLS(false), + ably.WithToken("fake:token"), + ably.WithUseTokenAuth(true), + ably.WithRealtimeHost(serverURL.Host)) + assert.NoError(t, err) + defer client.Close() + + expectedAgentHeaderValue := ably.AblySDKIdentifier + " " + ably.GoRuntimeIdentifier + " " + ably.GoOSIdentifier() + ablytest.Wait(ablytest.ConnWaiter(client, nil, ably.ConnectionEventDisconnected), nil) - expectedAgentHeaderValue := ably.AblySDKIdentifier + " " + ably.GoRuntimeIdentifier + " " + ably.GoOSIdentifier() + " bar" - ablytest.Wait(ablytest.ConnWaiter(client, nil, ably.ConnectionEventDisconnected), nil) + assert.Equal(t, expectedAgentHeaderValue, agentHeaderValue) + }) + + t.Run("RSC7d6 : Should set ablyAgent header with custom agents", func(t *testing.T) { + var agentHeaderValue string + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + agentHeaderValue = r.Header.Get(ably.AblyAgentHeader) + w.WriteHeader(http.StatusInternalServerError) + })) + defer server.Close() + serverURL, err := url.Parse(server.URL) + assert.NoError(t, err) - assert.Equal(t, expectedAgentHeaderValue, agentHeaderValue) + client, err := ably.NewRealtime( + ably.WithEnvironment(ablytest.Environment), + ably.WithTLS(false), + ably.WithToken("fake:token"), + ably.WithUseTokenAuth(true), + ably.WithRealtimeHost(serverURL.Host), + ably.WithAgents(map[string]string{ + "foo": "1.2.3", + }), + ) + assert.NoError(t, err) + defer client.Close() + + expectedAgentHeaderValue := ably.AblySDKIdentifier + " " + ably.GoRuntimeIdentifier + " " + ably.GoOSIdentifier() + " foo/1.2.3" + ablytest.Wait(ablytest.ConnWaiter(client, nil, ably.ConnectionEventDisconnected), nil) + + assert.Equal(t, expectedAgentHeaderValue, agentHeaderValue) + }) + + t.Run("RSC7d6 : Should set ablyAgent header with custom agents missing version", func(t *testing.T) { + var agentHeaderValue string + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + agentHeaderValue = r.Header.Get(ably.AblyAgentHeader) + w.WriteHeader(http.StatusInternalServerError) + })) + defer server.Close() + serverURL, err := url.Parse(server.URL) + assert.NoError(t, err) + + client, err := ably.NewRealtime( + ably.WithEnvironment(ablytest.Environment), + ably.WithTLS(false), + ably.WithToken("fake:token"), + ably.WithUseTokenAuth(true), + ably.WithRealtimeHost(serverURL.Host), + ably.WithAgents(map[string]string{ + "bar": "", + }), + ) + assert.NoError(t, err) + defer client.Close() + + expectedAgentHeaderValue := ably.AblySDKIdentifier + " " + ably.GoRuntimeIdentifier + " " + ably.GoOSIdentifier() + " bar" + ablytest.Wait(ablytest.ConnWaiter(client, nil, ably.ConnectionEventDisconnected), nil) + + assert.Equal(t, expectedAgentHeaderValue, agentHeaderValue) + }) }) } @@ -161,7 +270,7 @@ func TestRealtime_RTN17_HostFallback(t *testing.T) { t.Run("RTN17a: First attempt should be made on default primary host", func(t *testing.T) { visitedHosts := initClientWithConnError(errors.New("host url is wrong")) - assert.Equal(t, "realtime.ably.io", visitedHosts[0]) + assert.Equal(t, "main.realtime.ably.net", visitedHosts[0]) }) t.Run("RTN17b: Fallback behaviour", func(t *testing.T) { @@ -169,7 +278,7 @@ func TestRealtime_RTN17_HostFallback(t *testing.T) { t.Run("apply when default realtime endpoint is not overridden, port/tlsport not set", func(t *testing.T) { visitedHosts := initClientWithConnError(getTimeoutErr()) - expectedPrimaryHost := "realtime.ably.io" + expectedPrimaryHost := "main.realtime.ably.net" expectedFallbackHosts := ably.DefaultFallbackHosts() assert.Equal(t, 6, len(visitedHosts)) @@ -177,7 +286,15 @@ func TestRealtime_RTN17_HostFallback(t *testing.T) { assert.ElementsMatch(t, expectedFallbackHosts, visitedHosts[1:]) }) - t.Run("does not apply when the custom realtime endpoint is used", func(t *testing.T) { + t.Run("does not apply when endpoint with fqdn is used", func(t *testing.T) { + visitedHosts := initClientWithConnError(getTimeoutErr(), ably.WithEndpoint("custom-realtime.ably.io")) + expectedHost := "custom-realtime.ably.io" + + require.Equal(t, 1, len(visitedHosts)) + assert.Equal(t, expectedHost, visitedHosts[0]) + }) + + t.Run("does not apply when legacy custom realtimeHost is used", func(t *testing.T) { visitedHosts := initClientWithConnError(getTimeoutErr(), ably.WithRealtimeHost("custom-realtime.ably.io")) expectedHost := "custom-realtime.ably.io" @@ -188,14 +305,28 @@ func TestRealtime_RTN17_HostFallback(t *testing.T) { t.Run("apply when fallbacks are provided", func(t *testing.T) { fallbacks := []string{"fallback0", "fallback1", "fallback2"} visitedHosts := initClientWithConnError(getTimeoutErr(), ably.WithFallbackHosts(fallbacks)) - expectedPrimaryHost := "realtime.ably.io" + expectedPrimaryHost := "main.realtime.ably.net" assert.Equal(t, 4, len(visitedHosts)) assert.Equal(t, expectedPrimaryHost, visitedHosts[0]) assert.ElementsMatch(t, fallbacks, visitedHosts[1:]) }) - t.Run("apply when fallbackHostUseDefault is true, even if env. or host is set", func(t *testing.T) { + t.Run("apply when fallbackHostUseDefault is true, even if endpoint option is used", func(t *testing.T) { + visitedHosts := initClientWithConnError( + getTimeoutErr(), + ably.WithFallbackHostsUseDefault(true), + ably.WithEndpoint("custom")) + + expectedPrimaryHost := "custom.realtime.ably.net" + expectedFallbackHosts := ably.DefaultFallbackHosts() + + assert.Equal(t, 6, len(visitedHosts)) + assert.Equal(t, expectedPrimaryHost, visitedHosts[0]) + assert.ElementsMatch(t, expectedFallbackHosts, visitedHosts[1:]) + }) + + t.Run("apply when fallbackHostUseDefault is true, even if legacy environment or realtimeHost is set", func(t *testing.T) { visitedHosts := initClientWithConnError( getTimeoutErr(), ably.WithFallbackHostsUseDefault(true), diff --git a/ably/realtime_conn.go b/ably/realtime_conn.go index 284ee0bfc..91a2bfda2 100644 --- a/ably/realtime_conn.go +++ b/ably/realtime_conn.go @@ -388,6 +388,7 @@ func (c *Connection) connectWith(arg connArgs) (result, error) { var conn conn primaryHost := c.opts.getRealtimeHost() + hosts := []string{primaryHost} fallbackHosts, err := c.opts.getFallbackHosts() if err != nil { diff --git a/ablytest/ablytest.go b/ablytest/ablytest.go index fc7b83630..7a69f8448 100644 --- a/ablytest/ablytest.go +++ b/ablytest/ablytest.go @@ -18,6 +18,7 @@ var Timeout = 30 * time.Second var NoBinaryProtocol bool var DefaultLogLevel = ably.LogNone var Environment = "sandbox" +var Endpoint = "nonprod:sandbox" func nonil(err ...error) error { for _, err := range err { From ee7c1b1bd8016bb2ec1029aeebc39af977cd1f5a Mon Sep 17 00:00:00 2001 From: Laura Martin Date: Wed, 8 Jan 2025 09:48:20 +0000 Subject: [PATCH 02/12] fixup! Use endpoint as default connection option (ADR-119) --- ably/options.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ably/options.go b/ably/options.go index bd5c7bace..013885fd6 100644 --- a/ably/options.go +++ b/ably/options.go @@ -63,12 +63,14 @@ var defaultOptions = clientOptions{ LogLevel: LogWarning, // RSC2 } +var nonprodRegexp = regexp.MustCompile(`^nonprod:(.*)$`) + func defaultFallbackHosts() []string { return endpointFallbacks("main", "ably-realtime.com") } func getEndpointFallbackHosts(endpoint string) []string { - if match := regexp.MustCompile(`^nonprod:(.*)$`).FindStringSubmatch(endpoint); match != nil { + if match := nonprodRegexp.FindStringSubmatch(endpoint); match != nil { namespace := match[1] return endpointFallbacks(namespace, "ably-realtime-nonprod.com") } @@ -504,7 +506,7 @@ func (opts *clientOptions) getEndpoint() string { return endpoint } - if match := regexp.MustCompile(`^nonprod:(.*)$`).FindStringSubmatch(endpoint); match != nil { + if match := nonprodRegexp.FindStringSubmatch(endpoint); match != nil { namespace := match[1] return fmt.Sprintf("%s.realtime.ably-nonprod.net", namespace) } From f9e281f930ca0ca52358146f63d05d9ce9755e99 Mon Sep 17 00:00:00 2001 From: Laura Martin Date: Wed, 8 Jan 2025 11:37:36 +0000 Subject: [PATCH 03/12] fixup! Use endpoint as default connection option (ADR-119) --- ably/export_test.go | 12 ---- ably/options.go | 135 ++++++++++++++++------------------------ ably/options_test.go | 141 ++++++++++++++++++++++++------------------ ably/realtime_conn.go | 2 +- 4 files changed, 136 insertions(+), 154 deletions(-) diff --git a/ably/export_test.go b/ably/export_test.go index f936e0b08..891534fd0 100644 --- a/ably/export_test.go +++ b/ably/export_test.go @@ -12,10 +12,6 @@ func NewClientOptions(os ...ClientOption) *clientOptions { return applyOptionsWithDefaults(os...) } -func GetEnvFallbackHosts(env string) []string { - return getEnvFallbackHosts(env) -} - func GetEndpointFallbackHosts(endpoint string) []string { return getEndpointFallbackHosts(endpoint) } @@ -24,14 +20,6 @@ func (opts *clientOptions) GetEndpoint() string { return opts.getEndpoint() } -func (opts *clientOptions) GetRestHost() string { - return opts.getRestHost() -} - -func (opts *clientOptions) GetRealtimeHost() string { - return opts.getRealtimeHost() -} - func (opts *clientOptions) ActivePort() (int, bool) { return opts.activePort() } diff --git a/ably/options.go b/ably/options.go index 013885fd6..719a094a1 100644 --- a/ably/options.go +++ b/ably/options.go @@ -70,6 +70,10 @@ func defaultFallbackHosts() []string { } func getEndpointFallbackHosts(endpoint string) []string { + if endpoint == "sandbox" { + return endpointFallbacks("sandbox", "ably-realtime-nonprod.com") + } + if match := nonprodRegexp.FindStringSubmatch(endpoint); match != nil { namespace := match[1] return endpointFallbacks(namespace, "ably-realtime-nonprod.com") @@ -86,16 +90,6 @@ func endpointFallbacks(namespace, root string) []string { return fallbacks } -func getEnvFallbackHosts(env string) []string { - return []string{ - fmt.Sprintf("%s-%s", env, "a-fallback.ably-realtime.com"), - fmt.Sprintf("%s-%s", env, "b-fallback.ably-realtime.com"), - fmt.Sprintf("%s-%s", env, "c-fallback.ably-realtime.com"), - fmt.Sprintf("%s-%s", env, "d-fallback.ably-realtime.com"), - fmt.Sprintf("%s-%s", env, "e-fallback.ably-realtime.com"), - } -} - const ( authBasic = 1 + iota authToken @@ -262,17 +256,16 @@ type clientOptions struct { // authOptions Embedded an [ably.authOptions] object (TO3j). authOptions - // Endpoint specifies the domain used to connect to Ably. It has the following properties: - // If the endpoint option is a production routing policy name of the form [name] then the primary domain is [name].realtime.ably.net (REC1b4). - // If the endpoint option is a non-production routing policy name of the form nonprod:[name] then the primary domain is [name].realtime.ably-nonprod.net (REC1b3). - // If the endpoint option is a domain name, determined by it containing at least one period (.) then the primary domain is the value of the endpoint option (REC1b2). + // Endpoint specifies the domain used to connect to Ably (REC1a). + // If the endpoint option is specified then (REC1b): // If any one of the deprecated options environment, restHost, realtimeHost are also specified then the options as a set are invalid (REC1b1). + // If the endpoint option is a domain name, determined by it containing at least one period (.) then the primary domain is the value of the endpoint option (REC1b2). + // Otherwise, if the endpoint option is a non-production routing policy name of the form nonprod:[name] then the primary domain is [name].realtime.ably-nonprod.net (REC1b3). + // Otherwise, if the endpoint option is a production routing policy name of the form [name] then the primary domain is [name].realtime.ably.net (REC1b4). Endpoint string // Deprecated: this property is deprecated and will be removed in a future version. - // RESTHost enables a non-default Ably host to be specified. For - // development environments only. The default value is rest.ably.io (RSC12, - // TO3k2). + // If the restHost option is specified the primary domain is the value of the restHost option REC1d1). RESTHost string // Deprecated: this property is deprecated and will be removed in a future version. @@ -285,13 +278,13 @@ type clientOptions struct { FallbackHosts []string // Deprecated: this property is deprecated and will be removed in a future version. - // RealtimeHost enables a non-default Ably host to be specified for realtime connections. - // For development environments only. The default value is realtime.ably.io (RTC1d, TO3k3). + // If the realtimeHost option is specified the primary domain is the value of the realtimeHost option (REC1d2). RealtimeHost string // Deprecated: this property is deprecated and will be removed in a future version. - // Environment enables a custom environment to be used with the Ably service. - // Optional: prefixes both hostname with the environment string (RSC15b, TO3k1). + // If the deprecated environment option is specified then it defines a production routing policy name [name] (REC1c): + // If any one of the deprecated options restHost, realtimeHost are also specified then the options as a set are invalid (REC1c1). + // Otherwise, the primary domain is [name].realtime.ably.net (REC1c2). Environment string // Port is used for non-TLS connections and requests @@ -460,11 +453,6 @@ func (opts *clientOptions) validate() error { return nil } -func (opts *clientOptions) isProductionEnvironment() bool { - env := opts.Environment - return empty(env) || strings.EqualFold(env, "production") -} - func (opts *clientOptions) activePort() (port int, isDefault bool) { if opts.NoTLS { port = opts.Port @@ -486,65 +474,54 @@ func (opts *clientOptions) activePort() (port int, isDefault bool) { return } -func (opts *clientOptions) usingLegacyOpts() bool { - return empty(opts.Endpoint) && (!empty(opts.Environment) || !empty(opts.RESTHost) || !empty(opts.RealtimeHost)) -} - // endpointFqdn handles an endpoint that uses a hostname, which may be an IPv4 // address, IPv6 address or localhost func endpointFqdn(endpoint string) bool { return strings.Contains(endpoint, ".") || strings.Contains(endpoint, "::") || endpoint == "localhost" } -func (opts *clientOptions) getEndpoint() string { - endpoint := opts.Endpoint - if empty(endpoint) { - endpoint = defaultOptions.Endpoint - } - - if endpointFqdn(opts.Endpoint) { - return endpoint - } - - if match := nonprodRegexp.FindStringSubmatch(endpoint); match != nil { - namespace := match[1] - return fmt.Sprintf("%s.realtime.ably-nonprod.net", namespace) +func (opts *clientOptions) endpointValueWithLegacySupport() string { + if !empty(opts.Endpoint) { + return opts.Endpoint } - return fmt.Sprintf("%s.realtime.ably.net", endpoint) -} + if !empty(opts.Environment) { + if opts.Environment == "production" { + return defaultOptions.Endpoint + } -func (opts *clientOptions) getRestHost() string { - if !opts.usingLegacyOpts() { - return opts.getEndpoint() + return opts.Environment } if !empty(opts.RESTHost) { return opts.RESTHost } - if !opts.isProductionEnvironment() { - return opts.Environment + "-" + defaultOptions.RESTHost + + if !empty(opts.RealtimeHost) { + return opts.RealtimeHost } - return defaultOptions.RESTHost + + return defaultOptions.Endpoint } -func (opts *clientOptions) getRealtimeHost() string { - if !opts.usingLegacyOpts() { - return opts.getEndpoint() - } +// REC2 +func (opts *clientOptions) getEndpoint() string { + ep := opts.endpointValueWithLegacySupport() - if !empty(opts.RealtimeHost) { - return opts.RealtimeHost + if endpointFqdn(ep) { + return ep } - if !empty(opts.RESTHost) { - logger := opts.LogHandler - logger.Printf(LogWarning, "restHost is set to %s but realtimeHost is not set so setting realtimeHost to %s too. If this is not what you want, please set realtimeHost explicitly.", opts.RESTHost, opts.RealtimeHost) - return opts.RESTHost + + if ep == "sandbox" { + return "sandbox.realtime.ably-nonprod.net" } - if !opts.isProductionEnvironment() { - return opts.Environment + "-" + defaultOptions.RealtimeHost + + if match := nonprodRegexp.FindStringSubmatch(ep); match != nil { + namespace := match[1] + return fmt.Sprintf("%s.realtime.ably-nonprod.net", namespace) } - return defaultOptions.RealtimeHost + + return fmt.Sprintf("%s.realtime.ably.net", ep) } func empty(s string) bool { @@ -552,7 +529,7 @@ func empty(s string) bool { } func (opts *clientOptions) restURL() (restUrl string) { - baseUrl := opts.getRestHost() + baseUrl := opts.getEndpoint() _, _, err := net.SplitHostPort(baseUrl) if err != nil { // set port if not set in baseUrl port, _ := opts.activePort() @@ -595,18 +572,12 @@ func (opts *clientOptions) getFallbackHosts() ([]string, error) { return defaultOptions.FallbackHosts, nil } - if opts.FallbackHosts == nil && !empty(opts.Endpoint) { - if endpointFqdn(opts.Endpoint) { + if opts.FallbackHosts == nil { + ep := opts.endpointValueWithLegacySupport() + if endpointFqdn(ep) { return opts.FallbackHosts, nil } - return getEndpointFallbackHosts(opts.Endpoint), nil - } - - if opts.FallbackHosts == nil && empty(opts.RESTHost) && empty(opts.RealtimeHost) && isDefaultPort { - if opts.isProductionEnvironment() { - return defaultOptions.FallbackHosts, nil - } - return getEnvFallbackHosts(opts.Environment), nil + return getEndpointFallbackHosts(ep), nil } return opts.FallbackHosts, nil @@ -1153,11 +1124,12 @@ func WithEchoMessages(echo bool) ClientOption { } // WithEndpoint is used for setting Endpoint using [ably.ClientOption]. -// Endpoint specifies the domain used to connect to Ably. It has the following properties: -// If the endpoint option is a production routing policy name of the form [name] then the primary domain is [name].realtime.ably.net (REC1b4). -// If the endpoint option is a non-production routing policy name of the form nonprod:[name] then the primary domain is [name].realtime.ably-nonprod.net (REC1b3). -// If the endpoint option is a domain name, determined by it containing at least one period (.) then the primary domain is the value of the endpoint option (REC1b2). +// Endpoint specifies the domain used to connect to Ably (REC1a). +// If the endpoint option is specified then (REC1b): // If any one of the deprecated options environment, restHost, realtimeHost are also specified then the options as a set are invalid (REC1b1). +// If the endpoint option is a domain name, determined by it containing at least one period (.) then the primary domain is the value of the endpoint option (REC1b2). +// Otherwise, if the endpoint option is a non-production routing policy name of the form nonprod:[name] then the primary domain is [name].realtime.ably-nonprod.net (REC1b3). +// Otherwise, if the endpoint option is a production routing policy name of the form [name] then the primary domain is [name].realtime.ably.net (REC1b4). func WithEndpoint(env string) ClientOption { return func(os *clientOptions) { os.Endpoint = env @@ -1165,8 +1137,9 @@ func WithEndpoint(env string) ClientOption { } // WithEnvironment is used for setting Environment using [ably.ClientOption]. -// Environment enables a custom environment to be used with the Ably service. -// Optional: prefixes both hostname with the environment string (RSC15b, TO3k1). +// If the deprecated environment option is specified then it defines a production routing policy name [name] (REC1c): +// If any one of the deprecated options restHost, realtimeHost are also specified then the options as a set are invalid (REC1c1). +// Otherwise, the primary domain is [name].realtime.ably.net (REC1c2). func WithEnvironment(env string) ClientOption { return func(os *clientOptions) { os.Environment = env diff --git a/ably/options_test.go b/ably/options_test.go index af5be52f6..d9b736ac2 100644 --- a/ably/options_test.go +++ b/ably/options_test.go @@ -13,7 +13,7 @@ import ( "github.com/stretchr/testify/assert" ) -func TestDefaultFallbacks_RSC15h(t *testing.T) { +func TestDefaultFallbacks_REC2c(t *testing.T) { expectedFallBackHosts := []string{ "main.a.fallback.ably-realtime.com", "main.b.fallback.ably-realtime.com", @@ -25,7 +25,7 @@ func TestDefaultFallbacks_RSC15h(t *testing.T) { assert.Equal(t, expectedFallBackHosts, hosts) } -func TestEndpointFallbacks(t *testing.T) { +func TestEndpointFallbacks_REC2c(t *testing.T) { t.Run("standard endpoint", func(t *testing.T) { expectedFallBackHosts := []string{ "acme.a.fallback.ably-realtime.com", @@ -38,6 +38,18 @@ func TestEndpointFallbacks(t *testing.T) { assert.Equal(t, expectedFallBackHosts, hosts) }) + t.Run("sandbox endpoint", func(t *testing.T) { + expectedFallBackHosts := []string{ + "sandbox.a.fallback.ably-realtime-nonprod.com", + "sandbox.b.fallback.ably-realtime-nonprod.com", + "sandbox.c.fallback.ably-realtime-nonprod.com", + "sandbox.d.fallback.ably-realtime-nonprod.com", + "sandbox.e.fallback.ably-realtime-nonprod.com", + } + hosts := ably.GetEndpointFallbackHosts("sandbox") + assert.Equal(t, expectedFallBackHosts, hosts) + }) + t.Run("nonprod endpoint", func(t *testing.T) { expectedFallBackHosts := []string{ "acme.a.fallback.ably-realtime-nonprod.com", @@ -51,28 +63,15 @@ func TestEndpointFallbacks(t *testing.T) { }) } -func TestEnvFallbackHosts_RSC15i(t *testing.T) { - expectedFallBackHosts := []string{ - "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", - } - hosts := ably.GetEnvFallbackHosts("sandbox") - assert.Equal(t, expectedFallBackHosts, hosts) -} - func TestInternetConnectionCheck_RTN17c(t *testing.T) { clientOptions := ably.NewClientOptions() assert.True(t, clientOptions.HasActiveInternetConnection()) } -func TestHosts_RSC15b(t *testing.T) { - t.Run("with default options", func(t *testing.T) { +func TestHosts_REC1(t *testing.T) { + t.Run("REC1a with default options", func(t *testing.T) { clientOptions := ably.NewClientOptions() - assert.Equal(t, "main.realtime.ably.net", clientOptions.GetRealtimeHost()) - assert.Equal(t, "main.realtime.ably.net", clientOptions.GetRestHost()) + assert.Equal(t, "main.realtime.ably.net", clientOptions.GetEndpoint()) assert.False(t, clientOptions.NoTLS) port, isDefaultPort := clientOptions.ActivePort() assert.Equal(t, 443, port) @@ -81,10 +80,9 @@ func TestHosts_RSC15b(t *testing.T) { assert.Equal(t, ably.DefaultFallbackHosts(), fallbackHosts) }) - t.Run("with endpoint as a custom routing policy name", func(t *testing.T) { + t.Run("REC1b with endpoint as a custom routing policy name", func(t *testing.T) { clientOptions := ably.NewClientOptions(ably.WithEndpoint("acme")) - assert.Equal(t, "acme.realtime.ably.net", clientOptions.GetRestHost()) - assert.Equal(t, "acme.realtime.ably.net", clientOptions.GetRealtimeHost()) + assert.Equal(t, "acme.realtime.ably.net", clientOptions.GetEndpoint()) assert.False(t, clientOptions.NoTLS) port, isDefaultPort := clientOptions.ActivePort() assert.Equal(t, 443, port) @@ -93,10 +91,9 @@ func TestHosts_RSC15b(t *testing.T) { assert.Equal(t, ably.GetEndpointFallbackHosts("acme"), fallbackHosts) }) - t.Run("with endpoint as a nonprod routing policy name", func(t *testing.T) { + t.Run("REC1b3 with endpoint as a nonprod routing policy name", func(t *testing.T) { clientOptions := ably.NewClientOptions(ably.WithEndpoint("nonprod:acme")) - assert.Equal(t, "acme.realtime.ably-nonprod.net", clientOptions.GetRestHost()) - assert.Equal(t, "acme.realtime.ably-nonprod.net", clientOptions.GetRealtimeHost()) + assert.Equal(t, "acme.realtime.ably-nonprod.net", clientOptions.GetEndpoint()) assert.False(t, clientOptions.NoTLS) port, isDefaultPort := clientOptions.ActivePort() assert.Equal(t, 443, port) @@ -105,10 +102,9 @@ func TestHosts_RSC15b(t *testing.T) { assert.Equal(t, ably.GetEndpointFallbackHosts("nonprod:acme"), fallbackHosts) }) - t.Run("with endpoint as a fqdn with no fallbackHosts specified", func(t *testing.T) { + t.Run("REC1b2 with endpoint as a fqdn with no fallbackHosts specified", func(t *testing.T) { clientOptions := ably.NewClientOptions(ably.WithEndpoint("foo.example.com")) - assert.Equal(t, "foo.example.com", clientOptions.GetRestHost()) - assert.Equal(t, "foo.example.com", clientOptions.GetRealtimeHost()) + assert.Equal(t, "foo.example.com", clientOptions.GetEndpoint()) assert.False(t, clientOptions.NoTLS) port, isDefaultPort := clientOptions.ActivePort() assert.Equal(t, 443, port) @@ -118,10 +114,9 @@ func TestHosts_RSC15b(t *testing.T) { assert.Nil(t, fallbackHosts) }) - t.Run("with endpoint as a fqdn with fallbackHosts specified", func(t *testing.T) { + t.Run("REC1b2 REC2a2 with endpoint as a fqdn with fallbackHosts specified", func(t *testing.T) { clientOptions := ably.NewClientOptions(ably.WithEndpoint("foo.example.com"), ably.WithFallbackHosts([]string{"fallback.foo.example.com"})) - assert.Equal(t, "foo.example.com", clientOptions.GetRestHost()) - assert.Equal(t, "foo.example.com", clientOptions.GetRealtimeHost()) + assert.Equal(t, "foo.example.com", clientOptions.GetEndpoint()) assert.False(t, clientOptions.NoTLS) port, isDefaultPort := clientOptions.ActivePort() assert.Equal(t, 443, port) @@ -132,10 +127,9 @@ func TestHosts_RSC15b(t *testing.T) { }) t.Run("legacy support", func(t *testing.T) { - t.Run("RSC15h with production environment", func(t *testing.T) { + t.Run("REC1c with production environment", func(t *testing.T) { clientOptions := ably.NewClientOptions(ably.WithEnvironment("production")) - assert.Equal(t, "realtime.ably.io", clientOptions.GetRealtimeHost()) - assert.Equal(t, "rest.ably.io", clientOptions.GetRestHost()) + assert.Equal(t, "main.realtime.ably.net", clientOptions.GetEndpoint()) assert.False(t, clientOptions.NoTLS) port, isDefaultPort := clientOptions.ActivePort() assert.Equal(t, 443, port) @@ -144,22 +138,31 @@ func TestHosts_RSC15b(t *testing.T) { assert.Equal(t, ably.DefaultFallbackHosts(), fallbackHosts) }) - t.Run("RSC15g2 RTC1e with custom environment", func(t *testing.T) { + t.Run("REC1c with sandbox environment", func(t *testing.T) { clientOptions := ably.NewClientOptions(ably.WithEnvironment("sandbox")) - assert.Equal(t, "sandbox-realtime.ably.io", clientOptions.GetRealtimeHost()) - assert.Equal(t, "sandbox-rest.ably.io", clientOptions.GetRestHost()) + assert.Equal(t, "sandbox.realtime.ably-nonprod.net", clientOptions.GetEndpoint()) assert.False(t, clientOptions.NoTLS) port, isDefaultPort := clientOptions.ActivePort() assert.Equal(t, 443, port) assert.True(t, isDefaultPort) fallbackHosts, _ := clientOptions.GetFallbackHosts() - assert.Equal(t, ably.GetEnvFallbackHosts("sandbox"), fallbackHosts) + assert.Equal(t, ably.GetEndpointFallbackHosts("sandbox"), fallbackHosts) }) - t.Run("RSC15g4 RTC1e with custom environment and fallbackHostUseDefault", func(t *testing.T) { - clientOptions := ably.NewClientOptions(ably.WithEnvironment("sandbox"), ably.WithFallbackHostsUseDefault(true)) - assert.Equal(t, "sandbox-realtime.ably.io", clientOptions.GetRealtimeHost()) - assert.Equal(t, "sandbox-rest.ably.io", clientOptions.GetRestHost()) + t.Run("REC1c with custom environment", func(t *testing.T) { + clientOptions := ably.NewClientOptions(ably.WithEnvironment("acme")) + assert.Equal(t, "acme.realtime.ably.net", clientOptions.GetEndpoint()) + assert.False(t, clientOptions.NoTLS) + port, isDefaultPort := clientOptions.ActivePort() + assert.Equal(t, 443, port) + assert.True(t, isDefaultPort) + fallbackHosts, _ := clientOptions.GetFallbackHosts() + assert.Equal(t, ably.GetEndpointFallbackHosts("acme"), fallbackHosts) + }) + + t.Run("REC1c REC2a1 with custom environment and fallbackHostUseDefault", func(t *testing.T) { + clientOptions := ably.NewClientOptions(ably.WithEnvironment("acme"), ably.WithFallbackHostsUseDefault(true)) + assert.Equal(t, "acme.realtime.ably.net", clientOptions.GetEndpoint()) assert.False(t, clientOptions.NoTLS) port, isDefaultPort := clientOptions.ActivePort() assert.Equal(t, 443, port) @@ -168,26 +171,24 @@ func TestHosts_RSC15b(t *testing.T) { assert.Equal(t, ably.DefaultFallbackHosts(), fallbackHosts) }) - t.Run("RSC11b RTN17b RTC1e with legacy custom environment and non default ports", func(t *testing.T) { + t.Run("REC1c with legacy custom environment and non default ports", func(t *testing.T) { clientOptions := ably.NewClientOptions( ably.WithEnvironment("local"), ably.WithPort(8080), ably.WithTLSPort(8081), ) - assert.Equal(t, "local-realtime.ably.io", clientOptions.GetRealtimeHost()) - assert.Equal(t, "local-rest.ably.io", clientOptions.GetRestHost()) + assert.Equal(t, "local.realtime.ably.net", clientOptions.GetEndpoint()) assert.False(t, clientOptions.NoTLS) port, isDefaultPort := clientOptions.ActivePort() assert.Equal(t, 8081, port) assert.False(t, isDefaultPort) fallbackHosts, _ := clientOptions.GetFallbackHosts() - assert.Nil(t, fallbackHosts) + assert.Equal(t, ably.GetEndpointFallbackHosts("local"), fallbackHosts) }) - t.Run("RSC11 with custom rest host", func(t *testing.T) { + t.Run("REC1d1 with custom restHost", func(t *testing.T) { clientOptions := ably.NewClientOptions(ably.WithRESTHost("test.org")) - assert.Equal(t, "test.org", clientOptions.GetRealtimeHost()) - assert.Equal(t, "test.org", clientOptions.GetRestHost()) + assert.Equal(t, "test.org", clientOptions.GetEndpoint()) assert.False(t, clientOptions.NoTLS) port, isDefaultPort := clientOptions.ActivePort() assert.Equal(t, 443, port) @@ -196,10 +197,20 @@ func TestHosts_RSC15b(t *testing.T) { assert.Nil(t, fallbackHosts) }) - t.Run("RSC11 with custom rest host and realtime host", func(t *testing.T) { + t.Run("REC1d2 with custom realtimeHost", func(t *testing.T) { + clientOptions := ably.NewClientOptions(ably.WithRealtimeHost("ws.test.org")) + assert.Equal(t, "ws.test.org", clientOptions.GetEndpoint()) + assert.False(t, clientOptions.NoTLS) + port, isDefaultPort := clientOptions.ActivePort() + assert.Equal(t, 443, port) + assert.True(t, isDefaultPort) + fallbackHosts, _ := clientOptions.GetFallbackHosts() + assert.Nil(t, fallbackHosts) + }) + + t.Run("REC1d with custom restHost and realtimeHost", func(t *testing.T) { clientOptions := ably.NewClientOptions(ably.WithRealtimeHost("ws.test.org"), ably.WithRESTHost("test.org")) - assert.Equal(t, "ws.test.org", clientOptions.GetRealtimeHost()) - assert.Equal(t, "test.org", clientOptions.GetRestHost()) + assert.Equal(t, "test.org", clientOptions.GetEndpoint()) assert.False(t, clientOptions.NoTLS) port, isDefaultPort := clientOptions.ActivePort() assert.Equal(t, 443, port) @@ -208,13 +219,12 @@ func TestHosts_RSC15b(t *testing.T) { assert.Nil(t, fallbackHosts) }) - t.Run("RSC15b with custom rest host and realtime host and fallbackHostsUseDefault", func(t *testing.T) { + t.Run("REC1d REC2b with custom restHost and realtimeHost and fallbackHostsUseDefault", func(t *testing.T) { clientOptions := ably.NewClientOptions( ably.WithRealtimeHost("ws.test.org"), ably.WithRESTHost("test.org"), ably.WithFallbackHostsUseDefault(true)) - assert.Equal(t, "ws.test.org", clientOptions.GetRealtimeHost()) - assert.Equal(t, "test.org", clientOptions.GetRestHost()) + assert.Equal(t, "test.org", clientOptions.GetEndpoint()) assert.False(t, clientOptions.NoTLS) port, isDefaultPort := clientOptions.ActivePort() assert.Equal(t, 443, port) @@ -224,10 +234,9 @@ func TestHosts_RSC15b(t *testing.T) { }) }) - t.Run("RSC15g1 with fallbackHosts", func(t *testing.T) { + t.Run("REC2a with fallbackHosts", func(t *testing.T) { clientOptions := ably.NewClientOptions(ably.WithFallbackHosts([]string{"a.example.com", "b.example.com"})) - assert.Equal(t, "main.realtime.ably.net", clientOptions.GetRealtimeHost()) - assert.Equal(t, "main.realtime.ably.net", clientOptions.GetRestHost()) + assert.Equal(t, "main.realtime.ably.net", clientOptions.GetEndpoint()) assert.False(t, clientOptions.NoTLS) port, isDefaultPort := clientOptions.ActivePort() assert.Equal(t, 443, port) @@ -236,7 +245,7 @@ func TestHosts_RSC15b(t *testing.T) { assert.Equal(t, []string{"a.example.com", "b.example.com"}, fallbackHosts) }) - t.Run("RSC15b with fallbackHosts and fallbackHostsUseDefault", func(t *testing.T) { + t.Run("REC2a1 with fallbackHosts and fallbackHostsUseDefault", func(t *testing.T) { clientOptions := ably.NewClientOptions( ably.WithFallbackHosts([]string{"a.example.com", "b.example.com"}), ably.WithFallbackHostsUseDefault(true)) @@ -245,7 +254,7 @@ func TestHosts_RSC15b(t *testing.T) { "fallbackHosts and fallbackHostsUseDefault cannot both be set") }) - t.Run("RSC15b with fallbackHostsUseDefault And custom port", func(t *testing.T) { + t.Run("REC2a1 with fallbackHostsUseDefault And custom port", func(t *testing.T) { clientOptions := ably.NewClientOptions(ably.WithTLSPort(8081), ably.WithFallbackHostsUseDefault(true)) _, isDefaultPort := clientOptions.ActivePort() assert.False(t, isDefaultPort) @@ -283,7 +292,19 @@ func TestClientOptions(t *testing.T) { "expected an error") }) t.Run("must return error on invalid combinations", func(t *testing.T) { - _, err := ably.NewREST([]ably.ClientOption{ably.WithEndpoint("acme"), ably.WithEnvironment("acme")}...) + _, err := ably.NewREST([]ably.ClientOption{ably.WithEndpoint("acme"), ably.WithEnvironment("acme"), ably.WithRealtimeHost("foo.example.com"), ably.WithRESTHost("foo.example.com")}...) + assert.Error(t, err, + "expected an error") + + _, err = ably.NewREST([]ably.ClientOption{ably.WithEndpoint("acme"), ably.WithEnvironment("acme")}...) + assert.Error(t, err, + "expected an error") + + _, err = ably.NewREST([]ably.ClientOption{ably.WithEnvironment("acme"), ably.WithRealtimeHost("foo.example.com")}...) + assert.Error(t, err, + "expected an error") + + _, err = ably.NewREST([]ably.ClientOption{ably.WithEnvironment("acme"), ably.WithRESTHost("foo.example.com")}...) assert.Error(t, err, "expected an error") diff --git a/ably/realtime_conn.go b/ably/realtime_conn.go index 91a2bfda2..7ec6fe3aa 100644 --- a/ably/realtime_conn.go +++ b/ably/realtime_conn.go @@ -387,7 +387,7 @@ func (c *Connection) connectWith(arg connArgs) (result, error) { } var conn conn - primaryHost := c.opts.getRealtimeHost() + primaryHost := c.opts.getEndpoint() hosts := []string{primaryHost} fallbackHosts, err := c.opts.getFallbackHosts() From b2c8165100e74c59d755f3e8b90a766e786f5db6 Mon Sep 17 00:00:00 2001 From: Laura Martin Date: Wed, 8 Jan 2025 11:45:42 +0000 Subject: [PATCH 04/12] fixup! fixup! Use endpoint as default connection option (ADR-119) --- ably/realtime_client_integration_test.go | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/ably/realtime_client_integration_test.go b/ably/realtime_client_integration_test.go index 0f69709bd..864ddfaf2 100644 --- a/ably/realtime_client_integration_test.go +++ b/ably/realtime_client_integration_test.go @@ -30,7 +30,7 @@ func TestRealtime_RealtimeHost(t *testing.T) { "::1", } - t.Run("with endpoint option", func(t *testing.T) { + t.Run("REC1b with endpoint option", func(t *testing.T) { for _, host := range hosts { dial := make(chan string, 1) client, err := ably.NewRealtime( @@ -52,7 +52,7 @@ func TestRealtime_RealtimeHost(t *testing.T) { } }) - t.Run("with legacy realtimeHost option", func(t *testing.T) { + t.Run("REC1d2 with legacy realtimeHost option", func(t *testing.T) { for _, host := range hosts { dial := make(chan string, 1) client, err := ably.NewRealtime( @@ -326,11 +326,10 @@ func TestRealtime_RTN17_HostFallback(t *testing.T) { assert.ElementsMatch(t, expectedFallbackHosts, visitedHosts[1:]) }) - t.Run("apply when fallbackHostUseDefault is true, even if legacy environment or realtimeHost is set", func(t *testing.T) { + t.Run("apply when fallbackHostUseDefault is true, even if legacy realtimeHost is set", func(t *testing.T) { visitedHosts := initClientWithConnError( getTimeoutErr(), ably.WithFallbackHostsUseDefault(true), - ably.WithEnvironment("custom"), ably.WithRealtimeHost("custom-ably.realtime.com")) expectedPrimaryHost := "custom-ably.realtime.com" @@ -384,7 +383,7 @@ func TestRealtime_RTN17_HostFallback(t *testing.T) { t.Fatalf("Error connecting host with error %v", err) } realtimeSuccessHost := realtimeMsgRecorder.URLs()[0].Hostname() - fallbackHosts := ably.GetEnvFallbackHosts("sandbox") + fallbackHosts := ably.GetEndpointFallbackHosts("sandbox") if !ablyutil.SliceContains(fallbackHosts, realtimeSuccessHost) { t.Fatalf("realtime host must be one of fallback hosts, received %v", realtimeSuccessHost) } From 538cd74a70b0a6a15148fd54aa28b83732a1e2da Mon Sep 17 00:00:00 2001 From: Laura Martin Date: Fri, 10 Jan 2025 11:19:03 +0000 Subject: [PATCH 05/12] fixup! fixup! fixup! Use endpoint as default connection option (ADR-119) --- ably/auth_integration_test.go | 8 +++---- ably/realtime_conn_spec_integration_test.go | 8 +++---- ably/rest_channel_integration_test.go | 6 ++--- ably/rest_client_integration_test.go | 13 ++++------- ablytest/sandbox.go | 25 ++++++++++++++++----- 5 files changed, 34 insertions(+), 26 deletions(-) diff --git a/ably/auth_integration_test.go b/ably/auth_integration_test.go index 4788f6b7c..290aae208 100644 --- a/ably/auth_integration_test.go +++ b/ably/auth_integration_test.go @@ -389,7 +389,7 @@ func TestAuth_JWT_Token_RSA8c(t *testing.T) { rec, optn := ablytest.NewHttpRecorder() rest, err := ably.NewREST( ably.WithToken(jwt), - ably.WithEnvironment(app.Environment), + ably.WithEndpoint(app.Endpoint), optn[0], ) assert.NoError(t, err, "rest()=%v", err) @@ -414,7 +414,7 @@ func TestAuth_JWT_Token_RSA8c(t *testing.T) { rest, err := ably.NewREST( ably.WithAuthURL(ablytest.CREATE_JWT_URL), ably.WithAuthParams(app.GetJwtAuthParams(30*time.Second, false)), - ably.WithEnvironment(app.Environment), + ably.WithEndpoint(app.Endpoint), optn[0], ) assert.NoError(t, err, "rest()=%v", err) @@ -457,7 +457,7 @@ func TestAuth_JWT_Token_RSA8c(t *testing.T) { rec, optn := ablytest.NewHttpRecorder() rest, err := ably.NewREST( - ably.WithEnvironment(app.Environment), + ably.WithEndpoint(app.Endpoint), authCallback, optn[0], ) @@ -485,7 +485,7 @@ func TestAuth_JWT_Token_RSA8c(t *testing.T) { rest, err := ably.NewREST( ably.WithAuthURL(ablytest.CREATE_JWT_URL), ably.WithAuthParams(app.GetJwtAuthParams(30*time.Second, true)), - ably.WithEnvironment(app.Environment), + ably.WithEndpoint(app.Endpoint), optn[0], ) assert.NoError(t, err, "rest()=%v", err) diff --git a/ably/realtime_conn_spec_integration_test.go b/ably/realtime_conn_spec_integration_test.go index 8af4b567a..143c73442 100644 --- a/ably/realtime_conn_spec_integration_test.go +++ b/ably/realtime_conn_spec_integration_test.go @@ -222,7 +222,7 @@ func Test_RTN4a_ConnectionEventForStateChange(t *testing.T) { t.Run(fmt.Sprintf("on %s", ably.ConnectionStateFailed), func(t *testing.T) { options := []ably.ClientOption{ - ably.WithEnvironment("sandbox"), + ably.WithEndpoint(ablytest.Endpoint), ably.WithAutoConnect(false), ably.WithKey("made:up"), } @@ -1735,7 +1735,7 @@ func TestRealtimeConn_RTN22a_RTN15h2_Integration_ServerInitiatedAuth(t *testing. realtime, err := ably.NewRealtime( ably.WithAutoConnect(false), ably.WithDial(recorder.Dial), - ably.WithEnvironment(ablytest.Environment), + ably.WithEndpoint(ablytest.Endpoint), ably.WithAuthCallback(authCallback)) assert.NoError(t, err) @@ -1798,7 +1798,7 @@ func TestRealtimeConn_RTN22_RTC8_Integration_ServerInitiatedAuth(t *testing.T) { ably.WithAutoConnect(false), ably.WithDial(recorder.Dial), ably.WithUseBinaryProtocol(false), - ably.WithEnvironment(ablytest.Environment), + ably.WithEndpoint(ablytest.Endpoint), ably.WithAuthCallback(authCallback)) assert.NoError(t, err) @@ -3037,7 +3037,7 @@ func TestRealtimeConn_RTC8a_ExplicitAuthorizeWhileConnected(t *testing.T) { realtimeMsgRecorder := NewMessageRecorder() realtime, err := ably.NewRealtime( ably.WithAutoConnect(false), - ably.WithEnvironment(ablytest.Environment), + ably.WithEndpoint(ablytest.Endpoint), ably.WithDial(realtimeMsgRecorder.Dial), ably.WithAuthCallback(authCallback)) diff --git a/ably/rest_channel_integration_test.go b/ably/rest_channel_integration_test.go index 925f18c40..f28e351bd 100644 --- a/ably/rest_channel_integration_test.go +++ b/ably/rest_channel_integration_test.go @@ -142,7 +142,7 @@ func TestRESTChannel(t *testing.T) { } func TestIdempotentPublishing(t *testing.T) { - app, err := ablytest.NewSandboxWithEnv(nil, ablytest.Environment) + app, err := ablytest.NewSandboxWithEndpoint(nil, ablytest.Endpoint, ablytest.Environment) assert.NoError(t, err) defer app.Close() options := app.Options(ably.WithIdempotentRESTPublishing(true)) @@ -295,7 +295,7 @@ func TestIdempotentPublishing(t *testing.T) { } func TestIdempotent_retry(t *testing.T) { - app, err := ablytest.NewSandboxWithEnv(nil, ablytest.Environment) + app, err := ablytest.NewSandboxWithEndpoint(nil, ablytest.Endpoint, ablytest.Environment) assert.NoError(t, err) defer app.Close() randomStr, err := ablyutil.BaseID() @@ -312,7 +312,7 @@ func TestIdempotent_retry(t *testing.T) { // failing all others via the test server fallbackHosts := []string{"fallback0", "fallback1", "fallback2"} nopts := []ably.ClientOption{ - ably.WithEnvironment(ablytest.Environment), + ably.WithEndpoint(ablytest.Endpoint), ably.WithTLS(false), ably.WithFallbackHosts(fallbackHosts), ably.WithIdempotentRESTPublishing(true), diff --git a/ably/rest_client_integration_test.go b/ably/rest_client_integration_test.go index 7b4f9eb57..6987201f0 100644 --- a/ably/rest_client_integration_test.go +++ b/ably/rest_client_integration_test.go @@ -250,7 +250,6 @@ func TestRest_RSC7_AblyAgent(t *testing.T) { assert.NoError(t, err) opts := []ably.ClientOption{ - ably.WithEnvironment(ablytest.Environment), ably.WithTLS(false), ably.WithUseTokenAuth(true), ably.WithRESTHost(serverURL.Host), @@ -275,7 +274,6 @@ func TestRest_RSC7_AblyAgent(t *testing.T) { assert.NoError(t, err) opts := []ably.ClientOption{ - ably.WithEnvironment(ablytest.Environment), ably.WithTLS(false), ably.WithUseTokenAuth(true), ably.WithRESTHost(serverURL.Host), @@ -303,7 +301,6 @@ func TestRest_RSC7_AblyAgent(t *testing.T) { assert.NoError(t, err) opts := []ably.ClientOption{ - ably.WithEnvironment(ablytest.Environment), ably.WithTLS(false), ably.WithUseTokenAuth(true), ably.WithRESTHost(serverURL.Host), @@ -346,13 +343,12 @@ func TestRest_RSC15_HostFallback(t *testing.T) { options := []ably.ClientOption{ ably.WithFallbackHosts(ably.DefaultFallbackHosts()), ably.WithTLS(false), - ably.WithEnvironment(""), // remove default sandbox env ably.WithHTTPMaxRetryCount(10), ably.WithUseTokenAuth(true), } retryCount, hosts := runTestServer(t, options) assert.Equal(t, 6, retryCount) // 1 primary and 5 default fallback hosts - assert.Equal(t, "rest.ably.io", hosts[0]) // primary host + assert.Equal(t, "main.realtime.ably.net", hosts[0]) // primary host assertSubset(t, ably.DefaultFallbackHosts(), hosts[1:]) // remaining fallback hosts assertUnique(t, hosts) // ensure all picked fallbacks are unique }) @@ -393,13 +389,12 @@ func TestRest_RSC15_HostFallback(t *testing.T) { options := []ably.ClientOption{ ably.WithFallbackHosts(ably.DefaultFallbackHosts()), ably.WithTLS(false), - ably.WithEnvironment(""), // remove default sandbox env ably.WithHTTPMaxRetryCount(10), ably.WithUseTokenAuth(true), } retryCount, hosts := runTestServerWithRequestTimeout(t, options) assert.Equal(t, 6, retryCount) // 1 primary and 5 default fallback hosts - assert.Equal(t, "rest.ably.io", hosts[0]) // primary host + assert.Equal(t, "main.realtime.ably.net", hosts[0]) // primary host assertSubset(t, ably.DefaultFallbackHosts(), hosts[1:]) // remaining fallback hosts assertUnique(t, hosts) // ensure all picked fallbacks are unique }) @@ -476,7 +471,7 @@ func TestRest_RSC15_HostFallback(t *testing.T) { t.Run("RSC15e must start with default host", func(t *testing.T) { options := []ably.ClientOption{ - ably.WithEnvironment("production"), + ably.WithEndpoint("main"), ably.WithTLS(false), ably.WithUseTokenAuth(true), } @@ -519,7 +514,7 @@ func TestRest_rememberHostFallback(t *testing.T) { defer server.Close() nopts = []ably.ClientOption{ - ably.WithEnvironment(ablytest.Environment), + ably.WithEndpoint(ablytest.Endpoint), ably.WithTLS(false), ably.WithFallbackHosts([]string{"fallback0", "fallback1", "fallback2"}), ably.WithUseTokenAuth(true), diff --git a/ablytest/sandbox.go b/ablytest/sandbox.go index 11220d037..046135733 100644 --- a/ablytest/sandbox.go +++ b/ablytest/sandbox.go @@ -13,6 +13,7 @@ import ( "net/url" "os" "path" + "regexp" "syscall" "time" @@ -99,7 +100,12 @@ var PresenceFixtures = func() []Presence { } type Sandbox struct { - Config *Config + Config *Config + + // Endpoint is the hostname to connect to + Endpoint string + + // Environment is used in auth parameters Environment string client *http.Client @@ -132,13 +138,14 @@ func MustSandbox(config *Config) *Sandbox { } func NewSandbox(config *Config) (*Sandbox, error) { - return NewSandboxWithEnv(config, Environment) + return NewSandboxWithEndpoint(config, Endpoint, Environment) } -func NewSandboxWithEnv(config *Config, env string) (*Sandbox, error) { +func NewSandboxWithEndpoint(config *Config, endpoint, environment string) (*Sandbox, error) { app := &Sandbox{ Config: config, - Environment: env, + Endpoint: endpoint, + Environment: environment, client: NewHTTPClient(), } if app.Config == nil { @@ -233,7 +240,7 @@ func (app *Sandbox) Options(opts ...ably.ClientOption) []ably.ClientOption { appHTTPClient := NewHTTPClient() appOpts := []ably.ClientOption{ ably.WithKey(app.Key()), - ably.WithEnvironment(app.Environment), + ably.WithEndpoint(app.Endpoint), ably.WithUseBinaryProtocol(!NoBinaryProtocol), ably.WithHTTPClient(appHTTPClient), ably.WithLogLevel(DefaultLogLevel), @@ -252,8 +259,14 @@ func (app *Sandbox) Options(opts ...ably.ClientOption) []ably.ClientOption { return appOpts } +var nonprodRegexp = regexp.MustCompile(`^nonprod:(.*)$`) + func (app *Sandbox) URL(paths ...string) string { - return "https://" + app.Environment + "-rest.ably.io/" + path.Join(paths...) + if match := nonprodRegexp.FindStringSubmatch(app.Endpoint); match != nil { + return fmt.Sprintf("https://%s.realtime.ably-nonprod.net/%s", match[1], path.Join(paths...)) + } + + return fmt.Sprintf("https://%s.realtime.ably.net/%s", app.Endpoint, path.Join(paths...)) } // Source code for the same => https://github.com/ably/echoserver/blob/main/app.js From 90c9a2bcf57260c53ea42f5d93cbc688c3c493d7 Mon Sep 17 00:00:00 2001 From: Laura Martin Date: Wed, 15 Jan 2025 10:17:20 +0000 Subject: [PATCH 06/12] fixup! fixup! fixup! fixup! Use endpoint as default connection option (ADR-119) --- ably/export_test.go | 4 +- ably/options.go | 64 ++++++++++++++-------------- ably/realtime_conn.go | 2 +- ably/rest_client_integration_test.go | 16 +++---- 4 files changed, 42 insertions(+), 44 deletions(-) diff --git a/ably/export_test.go b/ably/export_test.go index 891534fd0..932b6e06f 100644 --- a/ably/export_test.go +++ b/ably/export_test.go @@ -17,7 +17,7 @@ func GetEndpointFallbackHosts(endpoint string) []string { } func (opts *clientOptions) GetEndpoint() string { - return opts.getEndpoint() + return opts.getHostname() } func (opts *clientOptions) ActivePort() (int, bool) { @@ -189,7 +189,7 @@ func ApplyOptionsWithDefaults(o ...ClientOption) *clientOptions { } func EndpointFqdn(endpoint string) bool { - return endpointFqdn(endpoint) + return isEndpointFQDN(endpoint) } type ConnStateChanges = connStateChanges diff --git a/ably/options.go b/ably/options.go index 719a094a1..c40713ac3 100644 --- a/ably/options.go +++ b/ably/options.go @@ -12,7 +12,6 @@ import ( "net/http/httptrace" "net/url" "os" - "regexp" "strconv" "strings" "time" @@ -63,8 +62,6 @@ var defaultOptions = clientOptions{ LogLevel: LogWarning, // RSC2 } -var nonprodRegexp = regexp.MustCompile(`^nonprod:(.*)$`) - func defaultFallbackHosts() []string { return endpointFallbacks("main", "ably-realtime.com") } @@ -74,8 +71,8 @@ func getEndpointFallbackHosts(endpoint string) []string { return endpointFallbacks("sandbox", "ably-realtime-nonprod.com") } - if match := nonprodRegexp.FindStringSubmatch(endpoint); match != nil { - namespace := match[1] + if strings.HasPrefix(endpoint, "nonprod:") { + namespace := strings.TrimPrefix(endpoint, "nonprod:") return endpointFallbacks(namespace, "ably-realtime-nonprod.com") } @@ -83,9 +80,9 @@ func getEndpointFallbackHosts(endpoint string) []string { } func endpointFallbacks(namespace, root string) []string { - var fallbacks []string - for _, id := range []string{"a", "b", "c", "d", "e"} { - fallbacks = append(fallbacks, fmt.Sprintf("%s.%s.fallback.%s", namespace, id, root)) + fallbacks := make([]string, 5) + for i, id := range []string{"a", "b", "c", "d", "e"} { + fallbacks[i] = fmt.Sprintf("%s.%s.fallback.%s", namespace, id, root) } return fallbacks } @@ -256,12 +253,7 @@ type clientOptions struct { // authOptions Embedded an [ably.authOptions] object (TO3j). authOptions - // Endpoint specifies the domain used to connect to Ably (REC1a). - // If the endpoint option is specified then (REC1b): - // If any one of the deprecated options environment, restHost, realtimeHost are also specified then the options as a set are invalid (REC1b1). - // If the endpoint option is a domain name, determined by it containing at least one period (.) then the primary domain is the value of the endpoint option (REC1b2). - // Otherwise, if the endpoint option is a non-production routing policy name of the form nonprod:[name] then the primary domain is [name].realtime.ably-nonprod.net (REC1b3). - // Otherwise, if the endpoint option is a production routing policy name of the form [name] then the primary domain is [name].realtime.ably.net (REC1b4). + // Endpoint specifies either a routing policy name or fully qualified domain name to connect to Ably. Endpoint string // Deprecated: this property is deprecated and will be removed in a future version. @@ -474,9 +466,9 @@ func (opts *clientOptions) activePort() (port int, isDefault bool) { return } -// endpointFqdn handles an endpoint that uses a hostname, which may be an IPv4 +// isEndpointFQDN handles an endpoint that uses a hostname, which may be an IPv4 // address, IPv6 address or localhost -func endpointFqdn(endpoint string) bool { +func isEndpointFQDN(endpoint string) bool { return strings.Contains(endpoint, ".") || strings.Contains(endpoint, "::") || endpoint == "localhost" } @@ -505,10 +497,10 @@ func (opts *clientOptions) endpointValueWithLegacySupport() string { } // REC2 -func (opts *clientOptions) getEndpoint() string { +func (opts *clientOptions) getHostname() string { ep := opts.endpointValueWithLegacySupport() - if endpointFqdn(ep) { + if isEndpointFQDN(ep) { return ep } @@ -516,8 +508,8 @@ func (opts *clientOptions) getEndpoint() string { return "sandbox.realtime.ably-nonprod.net" } - if match := nonprodRegexp.FindStringSubmatch(ep); match != nil { - namespace := match[1] + if strings.HasPrefix(endpoint, "nonprod:") { + namespace := strings.TrimPrefix(endpoint, "nonprod:") return fmt.Sprintf("%s.realtime.ably-nonprod.net", namespace) } @@ -529,7 +521,7 @@ func empty(s string) bool { } func (opts *clientOptions) restURL() (restUrl string) { - baseUrl := opts.getEndpoint() + baseUrl := opts.getHostname() _, _, err := net.SplitHostPort(baseUrl) if err != nil { // set port if not set in baseUrl port, _ := opts.activePort() @@ -574,7 +566,7 @@ func (opts *clientOptions) getFallbackHosts() ([]string, error) { if opts.FallbackHosts == nil { ep := opts.endpointValueWithLegacySupport() - if endpointFqdn(ep) { + if isEndpointFQDN(ep) { return opts.FallbackHosts, nil } return getEndpointFallbackHosts(ep), nil @@ -1123,23 +1115,23 @@ func WithEchoMessages(echo bool) ClientOption { } } -// WithEndpoint is used for setting Endpoint using [ably.ClientOption]. -// Endpoint specifies the domain used to connect to Ably (REC1a). -// If the endpoint option is specified then (REC1b): -// If any one of the deprecated options environment, restHost, realtimeHost are also specified then the options as a set are invalid (REC1b1). -// If the endpoint option is a domain name, determined by it containing at least one period (.) then the primary domain is the value of the endpoint option (REC1b2). -// Otherwise, if the endpoint option is a non-production routing policy name of the form nonprod:[name] then the primary domain is [name].realtime.ably-nonprod.net (REC1b3). -// Otherwise, if the endpoint option is a production routing policy name of the form [name] then the primary domain is [name].realtime.ably.net (REC1b4). +// WithEndpoint sets a custom endpoint for connecting to the Ably service (see +// [Platform Customization] for more information). +// +// [Platform Customization]: https://ably.com/docs/platform-customization func WithEndpoint(env string) ClientOption { return func(os *clientOptions) { os.Endpoint = env } } -// WithEnvironment is used for setting Environment using [ably.ClientOption]. -// If the deprecated environment option is specified then it defines a production routing policy name [name] (REC1c): -// If any one of the deprecated options restHost, realtimeHost are also specified then the options as a set are invalid (REC1c1). -// Otherwise, the primary domain is [name].realtime.ably.net (REC1c2). +// WithEndpoint sets a custom endpoint for connecting to the Ably service (see +// [Platform Customization] for more information). +// +// Deprecated: this option is deprecated and will be removed in a future +// version. +// +// [Platform Customization]: https://ably.com/docs/platform-customization func WithEnvironment(env string) ClientOption { return func(os *clientOptions) { os.Environment = env @@ -1197,6 +1189,9 @@ func WithQueueMessages(queue bool) ClientOption { // WithRESTHost is used for setting RESTHost using [ably.ClientOption]. // RESTHost enables a non-default Ably host to be specified. For development environments only. // The default value is rest.ably.io (RSC12, TO3k2). +// +// Deprecated: this option is deprecated and will be removed in a future +// version. func WithRESTHost(host string) ClientOption { return func(os *clientOptions) { os.RESTHost = host @@ -1216,6 +1211,9 @@ func WithHTTPRequestTimeout(timeout time.Duration) ClientOption { // WithRealtimeHost is used for setting RealtimeHost using [ably.ClientOption]. // RealtimeHost enables a non-default Ably host to be specified for realtime connections. // For development environments only. The default value is realtime.ably.io (RTC1d, TO3k3). +// +// Deprecated: this option is deprecated and will be removed in a future +// version. func WithRealtimeHost(host string) ClientOption { return func(os *clientOptions) { os.RealtimeHost = host diff --git a/ably/realtime_conn.go b/ably/realtime_conn.go index 7ec6fe3aa..f1fc2e569 100644 --- a/ably/realtime_conn.go +++ b/ably/realtime_conn.go @@ -387,7 +387,7 @@ func (c *Connection) connectWith(arg connArgs) (result, error) { } var conn conn - primaryHost := c.opts.getEndpoint() + primaryHost := c.opts.getHostname() hosts := []string{primaryHost} fallbackHosts, err := c.opts.getFallbackHosts() diff --git a/ably/rest_client_integration_test.go b/ably/rest_client_integration_test.go index 6987201f0..823d0824f 100644 --- a/ably/rest_client_integration_test.go +++ b/ably/rest_client_integration_test.go @@ -250,9 +250,9 @@ func TestRest_RSC7_AblyAgent(t *testing.T) { assert.NoError(t, err) opts := []ably.ClientOption{ + ably.WithEndpoint(serverURL.Host), ably.WithTLS(false), ably.WithUseTokenAuth(true), - ably.WithRESTHost(serverURL.Host), } client, err := ably.NewREST(opts...) @@ -274,9 +274,9 @@ func TestRest_RSC7_AblyAgent(t *testing.T) { assert.NoError(t, err) opts := []ably.ClientOption{ + ably.WithEndpoint(serverURL.Host), ably.WithTLS(false), ably.WithUseTokenAuth(true), - ably.WithRESTHost(serverURL.Host), ably.WithAgents(map[string]string{ "foo": "1.2.3", }), @@ -301,9 +301,9 @@ func TestRest_RSC7_AblyAgent(t *testing.T) { assert.NoError(t, err) opts := []ably.ClientOption{ + ably.WithEndpoint(serverURL.Host), ably.WithTLS(false), ably.WithUseTokenAuth(true), - ably.WithRESTHost(serverURL.Host), ably.WithAgents(map[string]string{ "bar": "", }), @@ -401,8 +401,8 @@ func TestRest_RSC15_HostFallback(t *testing.T) { t.Run("RSC15l1 must use alternative host on host unresolvable or unreachable", func(t *testing.T) { options := []ably.ClientOption{ + ably.WithEndpoint("foobar.ably.com"), ably.WithFallbackHosts(ably.DefaultFallbackHosts()), - ably.WithRESTHost("foobar.ably.com"), ably.WithFallbackHosts([]string{ "spam.ably.com", "tatto.ably.com", @@ -425,7 +425,7 @@ func TestRest_RSC15_HostFallback(t *testing.T) { options := []ably.ClientOption{ ably.WithTLS(false), - ably.WithRESTHost("example.com"), + ably.WithEndpoint("example.com"), ably.WithUseTokenAuth(true), } retryCount, hosts := runTestServer(t, options) @@ -439,7 +439,7 @@ func TestRest_RSC15_HostFallback(t *testing.T) { options := []ably.ClientOption{ ably.WithTLS(false), - ably.WithRESTHost("example.com"), + ably.WithEndpoint("example.com"), ably.WithFallbackHosts(ably.DefaultFallbackHosts()), ably.WithUseTokenAuth(true), } @@ -455,7 +455,7 @@ func TestRest_RSC15_HostFallback(t *testing.T) { t.Run("must occur when fallbackHosts is set", func(t *testing.T) { options := []ably.ClientOption{ ably.WithTLS(false), - ably.WithRESTHost("example.com"), + ably.WithEndpoint("example.com"), ably.WithFallbackHosts([]string{"a.example.com"}), ably.WithUseTokenAuth(true), } @@ -487,7 +487,7 @@ func TestRest_RSC15_HostFallback(t *testing.T) { options := []ably.ClientOption{ ably.WithTLS(false), - ably.WithRESTHost("example.com"), + ably.WithEndpoint("example.com"), ably.WithFallbackHosts([]string{}), ably.WithUseTokenAuth(true), } From d8736be0f5ecd8ec20e2f8c2181c3a2ccb15ece1 Mon Sep 17 00:00:00 2001 From: Laura Martin Date: Wed, 15 Jan 2025 10:31:51 +0000 Subject: [PATCH 07/12] fixup! fixup! fixup! fixup! fixup! Use endpoint as default connection option (ADR-119) --- ably/export_test.go | 2 +- ably/options_test.go | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/ably/export_test.go b/ably/export_test.go index 932b6e06f..2367fe05f 100644 --- a/ably/export_test.go +++ b/ably/export_test.go @@ -188,7 +188,7 @@ func ApplyOptionsWithDefaults(o ...ClientOption) *clientOptions { return applyOptionsWithDefaults(o...) } -func EndpointFqdn(endpoint string) bool { +func IsEndpointFQDN(endpoint string) bool { return isEndpointFQDN(endpoint) } diff --git a/ably/options_test.go b/ably/options_test.go index d9b736ac2..50c59fe1e 100644 --- a/ably/options_test.go +++ b/ably/options_test.go @@ -441,9 +441,9 @@ func TestPaginateParams(t *testing.T) { }) } -func TestEndpointFqdn(t *testing.T) { - assert.Equal(t, false, ably.EndpointFqdn("sandbox")) - assert.Equal(t, true, ably.EndpointFqdn("sandbox.example.com")) - assert.Equal(t, true, ably.EndpointFqdn("127.0.0.1")) - assert.Equal(t, true, ably.EndpointFqdn("localhost")) +func TestIsEndpointFQDN(t *testing.T) { + assert.Equal(t, false, ably.IsEndpointFQDN("sandbox")) + assert.Equal(t, true, ably.IsEndpointFQDN("sandbox.example.com")) + assert.Equal(t, true, ably.IsEndpointFQDN("127.0.0.1")) + assert.Equal(t, true, ably.IsEndpointFQDN("localhost")) } From bd8d1de30c06005e79ba79725b6b667ad888c17f Mon Sep 17 00:00:00 2001 From: Laura Martin Date: Wed, 15 Jan 2025 11:16:42 +0000 Subject: [PATCH 08/12] fixup! fixup! fixup! fixup! fixup! fixup! Use endpoint as default connection option (ADR-119) --- ably/options.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/ably/options.go b/ably/options.go index c40713ac3..6a16baf4e 100644 --- a/ably/options.go +++ b/ably/options.go @@ -23,8 +23,8 @@ const ( protocolJSON = "application/json" protocolMsgPack = "application/x-msgpack" - // endpoint is the default routing policy used to connect to Ably - endpoint = "main" + // defaultEndpoint is the default routing policy used to connect to Ably + defaultEndpoint = "main" // restHost is the primary ably host. restHost = "rest.ably.io" @@ -40,7 +40,7 @@ const ( ) var defaultOptions = clientOptions{ - Endpoint: endpoint, + Endpoint: defaultEndpoint, RESTHost: restHost, FallbackHosts: defaultFallbackHosts(), HTTPMaxRetryCount: 3, @@ -498,13 +498,13 @@ func (opts *clientOptions) endpointValueWithLegacySupport() string { // REC2 func (opts *clientOptions) getHostname() string { - ep := opts.endpointValueWithLegacySupport() + endpoint := opts.endpointValueWithLegacySupport() - if isEndpointFQDN(ep) { - return ep + if isEndpointFQDN(endpoint) { + return endpoint } - if ep == "sandbox" { + if endpoint == "sandbox" { return "sandbox.realtime.ably-nonprod.net" } @@ -513,7 +513,7 @@ func (opts *clientOptions) getHostname() string { return fmt.Sprintf("%s.realtime.ably-nonprod.net", namespace) } - return fmt.Sprintf("%s.realtime.ably.net", ep) + return fmt.Sprintf("%s.realtime.ably.net", endpoint) } func empty(s string) bool { From 466dbdf4b51703b492548638e53b3fc70888a2f6 Mon Sep 17 00:00:00 2001 From: Laura Martin Date: Wed, 15 Jan 2025 11:37:38 +0000 Subject: [PATCH 09/12] fixup! fixup! fixup! fixup! fixup! fixup! fixup! Use endpoint as default connection option (ADR-119) --- ably/ably_test.go | 2 +- ably/error_test.go | 4 ++-- ably/http_paginated_response_integration_test.go | 2 +- ably/realtime_client_integration_test.go | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/ably/ably_test.go b/ably/ably_test.go index 7a5384241..310fcedb1 100644 --- a/ably/ably_test.go +++ b/ably/ably_test.go @@ -356,7 +356,7 @@ func NewRecorder(httpClient *http.Client) *HostRecorder { func (hr *HostRecorder) Options(host string, opts ...ably.ClientOption) []ably.ClientOption { return append(opts, - ably.WithRealtimeHost(host), + ably.WithEndpoint(host), ably.WithAutoConnect(false), ably.WithDial(hr.dialWS), ably.WithHTTPClient(hr.httpClient), diff --git a/ably/error_test.go b/ably/error_test.go index c5eb860d8..fe7ea9e0b 100644 --- a/ably/error_test.go +++ b/ably/error_test.go @@ -54,7 +54,7 @@ func TestIssue127ErrorResponse(t *testing.T) { ably.WithKey("xxxxxxx.yyyyyyy:zzzzzzz"), ably.WithTLS(false), ably.WithUseTokenAuth(true), - ably.WithRESTHost(endpointURL.Hostname()), + ably.WithEndpoint(endpointURL.Hostname()), } port, _ := strconv.ParseInt(endpointURL.Port(), 10, 0) opts = append(opts, ably.WithPort(int(port))) @@ -134,7 +134,7 @@ func TestIssue_154(t *testing.T) { ably.WithKey("xxxxxxx.yyyyyyy:zzzzzzz"), ably.WithTLS(false), ably.WithUseTokenAuth(true), - ably.WithRESTHost(endpointURL.Hostname()), + ably.WithEndpoint(endpointURL.Hostname()), } port, _ := strconv.ParseInt(endpointURL.Port(), 10, 0) opts = append(opts, ably.WithPort(int(port))) diff --git a/ably/http_paginated_response_integration_test.go b/ably/http_paginated_response_integration_test.go index d47285b29..aac7fa31e 100644 --- a/ably/http_paginated_response_integration_test.go +++ b/ably/http_paginated_response_integration_test.go @@ -21,7 +21,7 @@ func TestHTTPPaginatedFallback(t *testing.T) { assert.NoError(t, err) defer app.Close() opts := app.Options(ably.WithUseBinaryProtocol(false), - ably.WithRESTHost("ably.invalid"), + ably.WithEndpoint("ably.invalid"), ably.WithFallbackHosts(nil)) client, err := ably.NewREST(opts...) assert.NoError(t, err) diff --git a/ably/realtime_client_integration_test.go b/ably/realtime_client_integration_test.go index 864ddfaf2..4b14d1a2e 100644 --- a/ably/realtime_client_integration_test.go +++ b/ably/realtime_client_integration_test.go @@ -424,7 +424,7 @@ func TestRealtime_RTN17_Integration_HostFallback_Internal_Server_Error(t *testin } return conn, err }), - ably.WithRealtimeHost(serverURL.Host)) + ably.WithEndpoint(serverURL.Host)) defer safeclose(t, ablytest.FullRealtimeCloser(realtime), app) @@ -471,7 +471,7 @@ func TestRealtime_RTN17_Integration_HostFallback_Timeout(t *testing.T) { } return conn, err }), - ably.WithRealtimeHost(serverURL.Host)) + ably.WithEndpoint(serverURL.Host)) defer safeclose(t, ablytest.FullRealtimeCloser(realtime), app) From 6ff3df0498a7eb5026d04d82c8026f1cd29f0393 Mon Sep 17 00:00:00 2001 From: Laura Martin Date: Wed, 15 Jan 2025 17:18:40 +0000 Subject: [PATCH 10/12] fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! Use endpoint as default connection option (ADR-119) --- ably/options.go | 8 ++++---- ably/realtime_client_integration_test.go | 3 --- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/ably/options.go b/ably/options.go index 6a16baf4e..d6d221e5c 100644 --- a/ably/options.go +++ b/ably/options.go @@ -466,8 +466,8 @@ func (opts *clientOptions) activePort() (port int, isDefault bool) { return } -// isEndpointFQDN handles an endpoint that uses a hostname, which may be an IPv4 -// address, IPv6 address or localhost +// isEndpointFQDN returns true if the given endpoint is a hostname, which may +// be an IPv4 address, IPv6 address or localhost func isEndpointFQDN(endpoint string) bool { return strings.Contains(endpoint, ".") || strings.Contains(endpoint, "::") || endpoint == "localhost" } @@ -1125,8 +1125,8 @@ func WithEndpoint(env string) ClientOption { } } -// WithEndpoint sets a custom endpoint for connecting to the Ably service (see -// [Platform Customization] for more information). +// WithEnvironment sets a custom endpoint for connecting to the Ably service +// (see [Platform Customization] for more information). // // Deprecated: this option is deprecated and will be removed in a future // version. diff --git a/ably/realtime_client_integration_test.go b/ably/realtime_client_integration_test.go index 4b14d1a2e..2bb048268 100644 --- a/ably/realtime_client_integration_test.go +++ b/ably/realtime_client_integration_test.go @@ -170,7 +170,6 @@ func TestRealtime_RSC7_AblyAgent(t *testing.T) { assert.NoError(t, err) client, err := ably.NewRealtime( - ably.WithEnvironment(ablytest.Environment), ably.WithTLS(false), ably.WithToken("fake:token"), ably.WithUseTokenAuth(true), @@ -195,7 +194,6 @@ func TestRealtime_RSC7_AblyAgent(t *testing.T) { assert.NoError(t, err) client, err := ably.NewRealtime( - ably.WithEnvironment(ablytest.Environment), ably.WithTLS(false), ably.WithToken("fake:token"), ably.WithUseTokenAuth(true), @@ -224,7 +222,6 @@ func TestRealtime_RSC7_AblyAgent(t *testing.T) { assert.NoError(t, err) client, err := ably.NewRealtime( - ably.WithEnvironment(ablytest.Environment), ably.WithTLS(false), ably.WithToken("fake:token"), ably.WithUseTokenAuth(true), From 997c2507e5cdcdb02e5d3e0ef609861b2528ff5d Mon Sep 17 00:00:00 2001 From: Laura Martin Date: Mon, 20 Jan 2025 10:35:19 +0000 Subject: [PATCH 11/12] fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! fixup! Use endpoint as default connection option (ADR-119) --- ably/export_test.go | 2 +- ably/options_test.go | 30 +++++++++++++++--------------- ablytest/sandbox.go | 9 ++++----- 3 files changed, 20 insertions(+), 21 deletions(-) diff --git a/ably/export_test.go b/ably/export_test.go index 2367fe05f..47d4a321e 100644 --- a/ably/export_test.go +++ b/ably/export_test.go @@ -16,7 +16,7 @@ func GetEndpointFallbackHosts(endpoint string) []string { return getEndpointFallbackHosts(endpoint) } -func (opts *clientOptions) GetEndpoint() string { +func (opts *clientOptions) GetHostname() string { return opts.getHostname() } diff --git a/ably/options_test.go b/ably/options_test.go index 50c59fe1e..0777cf432 100644 --- a/ably/options_test.go +++ b/ably/options_test.go @@ -71,7 +71,7 @@ func TestInternetConnectionCheck_RTN17c(t *testing.T) { func TestHosts_REC1(t *testing.T) { t.Run("REC1a with default options", func(t *testing.T) { clientOptions := ably.NewClientOptions() - assert.Equal(t, "main.realtime.ably.net", clientOptions.GetEndpoint()) + assert.Equal(t, "main.realtime.ably.net", clientOptions.GetHostname()) assert.False(t, clientOptions.NoTLS) port, isDefaultPort := clientOptions.ActivePort() assert.Equal(t, 443, port) @@ -82,7 +82,7 @@ func TestHosts_REC1(t *testing.T) { t.Run("REC1b with endpoint as a custom routing policy name", func(t *testing.T) { clientOptions := ably.NewClientOptions(ably.WithEndpoint("acme")) - assert.Equal(t, "acme.realtime.ably.net", clientOptions.GetEndpoint()) + assert.Equal(t, "acme.realtime.ably.net", clientOptions.GetHostname()) assert.False(t, clientOptions.NoTLS) port, isDefaultPort := clientOptions.ActivePort() assert.Equal(t, 443, port) @@ -93,7 +93,7 @@ func TestHosts_REC1(t *testing.T) { t.Run("REC1b3 with endpoint as a nonprod routing policy name", func(t *testing.T) { clientOptions := ably.NewClientOptions(ably.WithEndpoint("nonprod:acme")) - assert.Equal(t, "acme.realtime.ably-nonprod.net", clientOptions.GetEndpoint()) + assert.Equal(t, "acme.realtime.ably-nonprod.net", clientOptions.GetHostname()) assert.False(t, clientOptions.NoTLS) port, isDefaultPort := clientOptions.ActivePort() assert.Equal(t, 443, port) @@ -104,7 +104,7 @@ func TestHosts_REC1(t *testing.T) { t.Run("REC1b2 with endpoint as a fqdn with no fallbackHosts specified", func(t *testing.T) { clientOptions := ably.NewClientOptions(ably.WithEndpoint("foo.example.com")) - assert.Equal(t, "foo.example.com", clientOptions.GetEndpoint()) + assert.Equal(t, "foo.example.com", clientOptions.GetHostname()) assert.False(t, clientOptions.NoTLS) port, isDefaultPort := clientOptions.ActivePort() assert.Equal(t, 443, port) @@ -116,7 +116,7 @@ func TestHosts_REC1(t *testing.T) { t.Run("REC1b2 REC2a2 with endpoint as a fqdn with fallbackHosts specified", func(t *testing.T) { clientOptions := ably.NewClientOptions(ably.WithEndpoint("foo.example.com"), ably.WithFallbackHosts([]string{"fallback.foo.example.com"})) - assert.Equal(t, "foo.example.com", clientOptions.GetEndpoint()) + assert.Equal(t, "foo.example.com", clientOptions.GetHostname()) assert.False(t, clientOptions.NoTLS) port, isDefaultPort := clientOptions.ActivePort() assert.Equal(t, 443, port) @@ -129,7 +129,7 @@ func TestHosts_REC1(t *testing.T) { t.Run("legacy support", func(t *testing.T) { t.Run("REC1c with production environment", func(t *testing.T) { clientOptions := ably.NewClientOptions(ably.WithEnvironment("production")) - assert.Equal(t, "main.realtime.ably.net", clientOptions.GetEndpoint()) + assert.Equal(t, "main.realtime.ably.net", clientOptions.GetHostname()) assert.False(t, clientOptions.NoTLS) port, isDefaultPort := clientOptions.ActivePort() assert.Equal(t, 443, port) @@ -140,7 +140,7 @@ func TestHosts_REC1(t *testing.T) { t.Run("REC1c with sandbox environment", func(t *testing.T) { clientOptions := ably.NewClientOptions(ably.WithEnvironment("sandbox")) - assert.Equal(t, "sandbox.realtime.ably-nonprod.net", clientOptions.GetEndpoint()) + assert.Equal(t, "sandbox.realtime.ably-nonprod.net", clientOptions.GetHostname()) assert.False(t, clientOptions.NoTLS) port, isDefaultPort := clientOptions.ActivePort() assert.Equal(t, 443, port) @@ -151,7 +151,7 @@ func TestHosts_REC1(t *testing.T) { t.Run("REC1c with custom environment", func(t *testing.T) { clientOptions := ably.NewClientOptions(ably.WithEnvironment("acme")) - assert.Equal(t, "acme.realtime.ably.net", clientOptions.GetEndpoint()) + assert.Equal(t, "acme.realtime.ably.net", clientOptions.GetHostname()) assert.False(t, clientOptions.NoTLS) port, isDefaultPort := clientOptions.ActivePort() assert.Equal(t, 443, port) @@ -162,7 +162,7 @@ func TestHosts_REC1(t *testing.T) { t.Run("REC1c REC2a1 with custom environment and fallbackHostUseDefault", func(t *testing.T) { clientOptions := ably.NewClientOptions(ably.WithEnvironment("acme"), ably.WithFallbackHostsUseDefault(true)) - assert.Equal(t, "acme.realtime.ably.net", clientOptions.GetEndpoint()) + assert.Equal(t, "acme.realtime.ably.net", clientOptions.GetHostname()) assert.False(t, clientOptions.NoTLS) port, isDefaultPort := clientOptions.ActivePort() assert.Equal(t, 443, port) @@ -177,7 +177,7 @@ func TestHosts_REC1(t *testing.T) { ably.WithPort(8080), ably.WithTLSPort(8081), ) - assert.Equal(t, "local.realtime.ably.net", clientOptions.GetEndpoint()) + assert.Equal(t, "local.realtime.ably.net", clientOptions.GetHostname()) assert.False(t, clientOptions.NoTLS) port, isDefaultPort := clientOptions.ActivePort() assert.Equal(t, 8081, port) @@ -188,7 +188,7 @@ func TestHosts_REC1(t *testing.T) { t.Run("REC1d1 with custom restHost", func(t *testing.T) { clientOptions := ably.NewClientOptions(ably.WithRESTHost("test.org")) - assert.Equal(t, "test.org", clientOptions.GetEndpoint()) + assert.Equal(t, "test.org", clientOptions.GetHostname()) assert.False(t, clientOptions.NoTLS) port, isDefaultPort := clientOptions.ActivePort() assert.Equal(t, 443, port) @@ -199,7 +199,7 @@ func TestHosts_REC1(t *testing.T) { t.Run("REC1d2 with custom realtimeHost", func(t *testing.T) { clientOptions := ably.NewClientOptions(ably.WithRealtimeHost("ws.test.org")) - assert.Equal(t, "ws.test.org", clientOptions.GetEndpoint()) + assert.Equal(t, "ws.test.org", clientOptions.GetHostname()) assert.False(t, clientOptions.NoTLS) port, isDefaultPort := clientOptions.ActivePort() assert.Equal(t, 443, port) @@ -210,7 +210,7 @@ func TestHosts_REC1(t *testing.T) { t.Run("REC1d with custom restHost and realtimeHost", func(t *testing.T) { clientOptions := ably.NewClientOptions(ably.WithRealtimeHost("ws.test.org"), ably.WithRESTHost("test.org")) - assert.Equal(t, "test.org", clientOptions.GetEndpoint()) + assert.Equal(t, "test.org", clientOptions.GetHostname()) assert.False(t, clientOptions.NoTLS) port, isDefaultPort := clientOptions.ActivePort() assert.Equal(t, 443, port) @@ -224,7 +224,7 @@ func TestHosts_REC1(t *testing.T) { ably.WithRealtimeHost("ws.test.org"), ably.WithRESTHost("test.org"), ably.WithFallbackHostsUseDefault(true)) - assert.Equal(t, "test.org", clientOptions.GetEndpoint()) + assert.Equal(t, "test.org", clientOptions.GetHostname()) assert.False(t, clientOptions.NoTLS) port, isDefaultPort := clientOptions.ActivePort() assert.Equal(t, 443, port) @@ -236,7 +236,7 @@ func TestHosts_REC1(t *testing.T) { t.Run("REC2a with fallbackHosts", func(t *testing.T) { clientOptions := ably.NewClientOptions(ably.WithFallbackHosts([]string{"a.example.com", "b.example.com"})) - assert.Equal(t, "main.realtime.ably.net", clientOptions.GetEndpoint()) + assert.Equal(t, "main.realtime.ably.net", clientOptions.GetHostname()) assert.False(t, clientOptions.NoTLS) port, isDefaultPort := clientOptions.ActivePort() assert.Equal(t, 443, port) diff --git a/ablytest/sandbox.go b/ablytest/sandbox.go index 046135733..b7d65a4f0 100644 --- a/ablytest/sandbox.go +++ b/ablytest/sandbox.go @@ -13,7 +13,7 @@ import ( "net/url" "os" "path" - "regexp" + "strings" "syscall" "time" @@ -259,11 +259,10 @@ func (app *Sandbox) Options(opts ...ably.ClientOption) []ably.ClientOption { return appOpts } -var nonprodRegexp = regexp.MustCompile(`^nonprod:(.*)$`) - func (app *Sandbox) URL(paths ...string) string { - if match := nonprodRegexp.FindStringSubmatch(app.Endpoint); match != nil { - return fmt.Sprintf("https://%s.realtime.ably-nonprod.net/%s", match[1], path.Join(paths...)) + if strings.HasPrefix(app.Endpoint, "nonprod:") { + namespace := strings.TrimPrefix(app.Endpoint, "nonprod:") + return fmt.Sprintf("https://%s.realtime.ably-nonprod.net/%s", namespace, path.Join(paths...)) } return fmt.Sprintf("https://%s.realtime.ably.net/%s", app.Endpoint, path.Join(paths...)) From 727db4f8d714eb4c5481d548c73326529a2b4b8c Mon Sep 17 00:00:00 2001 From: Laura Martin Date: Thu, 23 Jan 2025 10:25:36 +0000 Subject: [PATCH 12/12] Remove sandbox backwards compatibility --- ably/options.go | 8 -------- 1 file changed, 8 deletions(-) diff --git a/ably/options.go b/ably/options.go index d6d221e5c..b1530a871 100644 --- a/ably/options.go +++ b/ably/options.go @@ -67,10 +67,6 @@ func defaultFallbackHosts() []string { } func getEndpointFallbackHosts(endpoint string) []string { - if endpoint == "sandbox" { - return endpointFallbacks("sandbox", "ably-realtime-nonprod.com") - } - if strings.HasPrefix(endpoint, "nonprod:") { namespace := strings.TrimPrefix(endpoint, "nonprod:") return endpointFallbacks(namespace, "ably-realtime-nonprod.com") @@ -504,10 +500,6 @@ func (opts *clientOptions) getHostname() string { return endpoint } - if endpoint == "sandbox" { - return "sandbox.realtime.ably-nonprod.net" - } - if strings.HasPrefix(endpoint, "nonprod:") { namespace := strings.TrimPrefix(endpoint, "nonprod:") return fmt.Sprintf("%s.realtime.ably-nonprod.net", namespace)