Skip to content

Commit

Permalink
wip! start investigating endpoint options
Browse files Browse the repository at this point in the history
  • Loading branch information
surminus committed Jan 6, 2025
1 parent 70acf92 commit 7ee78c8
Show file tree
Hide file tree
Showing 5 changed files with 273 additions and 82 deletions.
8 changes: 8 additions & 0 deletions ably/export_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
Expand Down
107 changes: 99 additions & 8 deletions ably/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"net/http/httptrace"
"net/url"
"os"
"regexp"
"strconv"
"strings"
"time"
Expand All @@ -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.
Expand All @@ -37,6 +41,7 @@ const (
)

var defaultOptions = clientOptions{
Endpoint: endpoint,
RESTHost: restHost,
FallbackHosts: defaultFallbackHosts(),
HTTPMaxRetryCount: 3,
Expand All @@ -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 {
Expand Down Expand Up @@ -244,8 +260,18 @@ 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).
// TODO: update docs with guidance
// 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.
Expand All @@ -257,10 +283,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
Expand Down Expand Up @@ -410,7 +438,15 @@ type clientOptions struct {
LogHandler Logger
}

// TODO: update for endpoint
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
Expand Down Expand Up @@ -446,7 +482,33 @@ 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))
}

func (opts *clientOptions) getEndpoint() string {
endpoint := opts.Endpoint
if empty(endpoint) {
endpoint = defaultOptions.Endpoint
}

if strings.Contains(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
}
Expand All @@ -457,6 +519,10 @@ func (opts *clientOptions) getRestHost() string {
}

func (opts *clientOptions) getRealtimeHost() string {
if !opts.usingLegacyOpts() {
return opts.getEndpoint()
}

if !empty(opts.RealtimeHost) {
return opts.RealtimeHost
}
Expand All @@ -475,6 +541,7 @@ func empty(s string) bool {
return len(strings.TrimSpace(s)) == 0
}

// TODO: update for endpoint
func (opts *clientOptions) restURL() (restUrl string) {
baseUrl := opts.getRestHost()
_, _, err := net.SplitHostPort(baseUrl)
Expand All @@ -501,9 +568,11 @@ func (opts *clientOptions) realtimeURL(realtimeHost string) (realtimeUrl string)
return "wss://" + baseUrl
}

// TODO: update for endpoint
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")
Expand All @@ -517,12 +586,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 strings.Contains(opts.Endpoint, ".") {
return nil, errors.New("fallbackHosts must be set is endpoint is fully qualified")
}
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
}

Expand Down Expand Up @@ -1066,6 +1144,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).
Expand Down Expand Up @@ -1319,6 +1409,7 @@ func WithDial(dial func(protocol string, u *url.URL, timeout time.Duration) (con
func applyOptionsWithDefaults(opts ...ClientOption) *clientOptions {
to := defaultOptions
// No need to set hosts by default
to.Endpoint = ""
to.RESTHost = ""
to.RealtimeHost = ""
to.FallbackHosts = nil
Expand Down
Loading

0 comments on commit 7ee78c8

Please sign in to comment.