diff --git a/.claude/skills/uts-to-kotlin/SKILL.md b/.claude/skills/uts-to-kotlin/SKILL.md index a7b90c7bd..f7f5368b8 100644 --- a/.claude/skills/uts-to-kotlin/SKILL.md +++ b/.claude/skills/uts-to-kotlin/SKILL.md @@ -676,13 +676,12 @@ Recognise a **proxy** spec by a reference to `create_proxy_session()`, proxy `ru A **direct-sandbox** spec (no `create_proxy_session`, no rules — just happy-path interop against `nonprod:sandbox`) uses the same `SandboxApp` provisioning and the same `runTest` / `@BeforeAll`+`runBlocking` lifecycle as a proxy test, but **drops all proxy wiring**: no `ProxyManager.ensureProxy()`, no `ProxySession`, no `connectThroughProxy`. The client connects straight to the sandbox host. `ChannelHistoryTest` (realtime) and `ObjectsLifecycleTest` (liveobjects) are the reference examples — read one before translating a direct-sandbox spec. -**Client wiring** — point both transports at the sandbox host (explicit hosts auto-disable fallback hosts, so no `fallbackHosts`): +**Client wiring** — point the client at the sandbox endpoint: ```kotlin private fun newClient(useBinaryProtocol: Boolean): AblyRealtime = TestRealtimeClient { key = app.defaultKey - realtimeHost = SandboxApp.sandboxHost // sandbox.realtime.ably-nonprod.net - restHost = SandboxApp.sandboxHost + endpoint = SandboxApp.sandboxEndpoint // nonprod:sandbox this.useBinaryProtocol = useBinaryProtocol autoConnect = false } @@ -728,7 +727,7 @@ Three helpers live under `uts/src/main/kotlin/io/ably/lib/uts/infra/integration/ - **`ProxyManager`** (`infra/integration/proxy/ProxyManager.kt`, package `io.ably.lib.uts.infra.integration.proxy`) — downloads/starts the shared `uts-proxy` process. Call `ProxyManager.ensureProxy()` once per suite in setup. - **`ProxySession`** (`infra/integration/proxy/ProxySession.kt`, same package) — one programmable session wrapping the proxy control API; also defines the `connectThroughProxy` extension and the rule-builder helpers. -- **`SandboxApp`** (`infra/integration/SandboxApp.kt`, package `io.ably.lib.uts.infra.integration`) — provisions/deletes a sandbox test app from the shared `test-app-setup.json` in ably-common. `SandboxApp.create()` returns a `SandboxApp` with `appId`, `defaultKey`, and `keys` (`defaultKey` is a full-capability `appId.keyId:keySecret`); `app.delete()` tears it down. Provision in suite setup, delete in teardown. Also owns the single upstream sandbox host constant `SandboxApp.sandboxHost` (`sandbox.realtime.ably-nonprod.net`, the resolved `nonprod:sandbox` endpoint) — the default target of every `ProxySession` (both `realtimeHost` and `restHost`), and what direct-sandbox clients set `realtimeHost` / `restHost` from. +- **`SandboxApp`** (`infra/integration/SandboxApp.kt`, package `io.ably.lib.uts.infra.integration`) — provisions/deletes a sandbox test app from the shared `test-app-setup.json` in ably-common. `SandboxApp.create()` returns a `SandboxApp` with `appId`, `defaultKey`, and `keys` (`defaultKey` is a full-capability `appId.keyId:keySecret`); `app.delete()` tears it down. Provision in suite setup, delete in teardown. Also owns `SandboxApp.sandboxEndpoint` (`nonprod:sandbox`), which direct-sandbox clients set as their `endpoint`, and `SandboxApp.sandboxHost` (`sandbox.realtime.ably-nonprod.net`, the hostname that endpoint resolves to) — the default upstream target of every `ProxySession`. Import what the tier needs: a **direct-sandbox** test imports `io.ably.lib.uts.infra.integration.SandboxApp` plus `io.ably.lib.uts.infra.unit.TestRealtimeClient` and `io.ably.lib.uts.infra.{awaitState, pollUntil}`; a **proxy** test additionally imports `io.ably.lib.uts.infra.integration.proxy.{ProxyManager, ProxySession, connectThroughProxy}`. @@ -799,11 +798,11 @@ val client = TestRealtimeClient { } ``` -ably-java has **no `endpoint` ClientOptions field**; `connectThroughProxy` sets the discrete host fields for you: +`connectThroughProxy` sets the proxy-def options for you: | Proxy-def option | What `connectThroughProxy` sets | |---|---| -| `endpoint: "localhost"` | `realtimeHost` **and** `restHost` = `session.proxyHost` (`"localhost"`) | +| `endpoint: "localhost"` | `endpoint = session.proxyHost` (`"localhost"`) | | `port: proxy_port` | `port = session.proxyPort` | | `tls: false` | `tls = false` | | `useBinaryProtocol: false` | already the `ClientOptionsBuilder` default — left untouched | diff --git a/.claude/skills/uts-to-kotlin/references/objects-mapping.md b/.claude/skills/uts-to-kotlin/references/objects-mapping.md index 26ab9b755..c97b2a8f9 100644 --- a/.claude/skills/uts-to-kotlin/references/objects-mapping.md +++ b/.claude/skills/uts-to-kotlin/references/objects-mapping.md @@ -623,10 +623,9 @@ REST provisioning" convention survives even though the module's internals are vi > envelope on the legacy `sandbox-rest.ably.io` host; both were aligned upstream — to this V2 shape and to > the canonical nonprod sandbox host `sandbox.realtime.ably-nonprod.net` — in ably/specification#497. > -> **Sandbox host.** `provisionObjectsViaRest` sets `restHost = SandboxApp.sandboxHost` -> (`sandbox.realtime.ably-nonprod.net`) — the same nonprod host `SandboxApp` and the realtime clients use, -> **not** `environment="sandbox"` (which resolves to the legacy `sandbox-rest.ably.io`, and -> can't be combined with `restHost` per `Hosts.java` TO3k2/TO3k3). The REST call hits the live sandbox +> **Sandbox host.** `provisionObjectsViaRest` sets `endpoint = SandboxApp.sandboxEndpoint` +> (`nonprod:sandbox`, resolving to `sandbox.realtime.ably-nonprod.net`) — the same endpoint `SandboxApp` +> and the realtime clients use. The REST call hits the live sandbox > today, and the realtime client observes the provisioned data through the SDK's OBJECT_SYNC + > `RealtimeObject.get()` (both implemented). diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0a832d082..d873e68f2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -190,11 +190,11 @@ for the JRE-specific library: ./gradlew core:testRealtimeSuite -To run tests against a specific host, specify in the environment: +To run tests against a specific endpoint, specify it in the environment: - env ABLY_ENV=staging ./gradlew testRealtimeSuite + env ABLY_ENDPOINT=nonprod:staging ./gradlew testRealtimeSuite -Tests will run against the sandbox environment by default. +Tests will run against the `nonprod:sandbox` endpoint by default. Tests can be run on the Android-specific library. An Android device must be connected, either a real device or the Android emulator. diff --git a/UPGRADING.md b/UPGRADING.md index a20aaf1ba..88d0ee8c3 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -149,3 +149,53 @@ find src -name '*.java' -o -name '*.kt' | xargs sed -i \ | `TokenDetails.fromJSON(…)`, `TokenRequest.fromJSON(…)` | `fromJsonElement(…)` | | `ClientOptions.fallbackHostsUseDefault` | Drop it. Default fallback hosts apply automatically; set `fallbackHosts` only for custom hosts. | | `RegistrationToken.Type.GCM` (Android) | `RegistrationToken.Type.FCM` | + +## 5. Choose the host with `endpoint` + +2.0 replaces `ClientOptions.environment`, `restHost` and `realtimeHost` with a single +`ClientOptions.endpoint` option, and the builder methods `environment(…)`, `restHost(…)` and +`realtimeHost(…)` with `endpoint(…)`. REST requests and the realtime connection now always use the +same host. + +| 1.x | 2.0 | +|-----|-----| +| No host options | No change; traffic moves to `main.realtime.ably.net` (see below) | +| `environment = "sandbox"` | `endpoint = "nonprod:sandbox"` | +| `environment = "acme"` (dedicated cluster) | `endpoint = "acme"` | +| `restHost` / `realtimeHost = "localhost"` | `endpoint = "localhost"` | +| `restHost` and `realtimeHost` set to the same custom host | `endpoint = ""` | +| `restHost` and `realtimeHost` set to different hosts | Not supported; use one host that serves both, or contact Ably | +| Custom `fallbackHosts` | Unchanged | + +How `endpoint` resolves: + +| `endpoint` | Primary host | Default fallback hosts | +|------------|--------------|------------------------| +| Unset | `main.realtime.ably.net` | `main.[a-e].fallback.ably-realtime.com` | +| A routing policy name, e.g. `acme` | `acme.realtime.ably.net` | `acme.[a-e].fallback.ably-realtime.com` | +| `nonprod:` plus a name, e.g. `nonprod:sandbox` | `sandbox.realtime.ably-nonprod.net` | `sandbox.[a-e].fallback.ably-realtime-nonprod.com` | +| A hostname (contains `.` or `::`, or is `localhost`) | The value as given | None | + +An explicit `fallbackHosts` always replaces the default fallback hosts. A custom `port` or `tlsPort` +no longer disables the default fallback hosts. + +**Before (1.x):** + +```java +ClientOptions options = new ClientOptions("xVLyHw.MHOCLg:..."); +options.environment = "sandbox"; +AblyRealtime realtime = new AblyRealtime(options); +``` + +**After (2.0):** + +```java +PubSubRealtimeClient realtime = PubSubServer.realtimeClientBuilder() + .key("xVLyHw.MHOCLg:...") + .endpoint("nonprod:sandbox") + .build(); +``` + +**Firewalls and proxies.** 1.x connected to `rest.ably.io`, `realtime.ably.io` and +`[a-e].ably-realtime.com` by default. If your network restricts outbound traffic, allow +`*.realtime.ably.net` and `*.fallback.ably-realtime.com` before upgrading. diff --git a/core-android/src/androidTest/java/io/ably/pubsub/test/android/AndroidSuite.java b/core-android/src/androidTest/java/io/ably/pubsub/test/android/AndroidSuite.java index cb8e5839c..4d5eabc19 100644 --- a/core-android/src/androidTest/java/io/ably/pubsub/test/android/AndroidSuite.java +++ b/core-android/src/androidTest/java/io/ably/pubsub/test/android/AndroidSuite.java @@ -56,7 +56,7 @@ public void android_http_header_test() { ClientOptions opts = new ClientOptions(testVars.keys[0].keyStr); opts.tls = false; opts.port = server.getListeningPort(); - opts.restHost = "localhost"; + opts.endpoint = "localhost"; PubSubHttpClient ably = HttpClientFactory.create(opts); ably.time(); diff --git a/core-android/src/main/java/io/ably/pubsub/push/ActivationContext.java b/core-android/src/main/java/io/ably/pubsub/push/ActivationContext.java index 464cb314b..a146bfbea 100644 --- a/core-android/src/main/java/io/ably/pubsub/push/ActivationContext.java +++ b/core-android/src/main/java/io/ably/pubsub/push/ActivationContext.java @@ -77,7 +77,7 @@ PubSubHttpClient getAbly() throws AblyException { throw AblyException.fromErrorInfo(new ErrorInfo("Unable to get Ably library instance; no device identity token", 40000, 400)); } Log.v(TAG, "getAbly(): returning Ably instance using deviceIdentityToken"); - // TODO: We need to persist Ably client options such as the environment with `deviceIdentityToken` and use these options during initialization. + // TODO: We need to persist Ably client options such as the endpoint with `deviceIdentityToken` and use these options during initialization. return (ably = HttpClientFactory.create(new ClientOptions(deviceIdentityToken))); } diff --git a/device/src/commonMain/kotlin/io/ably/pubsub/device/PubSubDevice.kt b/device/src/commonMain/kotlin/io/ably/pubsub/device/PubSubDevice.kt index 5b4fdfd1f..84d47ba71 100644 --- a/device/src/commonMain/kotlin/io/ably/pubsub/device/PubSubDevice.kt +++ b/device/src/commonMain/kotlin/io/ably/pubsub/device/PubSubDevice.kt @@ -193,12 +193,12 @@ public object PubSubDevice { public fun headers(headers: Map): ClientBuilder = apply { options.headers = headers } /** - * Sets [ClientOptions.restHost]. + * Sets [ClientOptions.endpoint]. * - * @param restHost the value to set. + * @param endpoint the value to set. * @return this builder. */ - public fun restHost(restHost: String): ClientBuilder = apply { options.restHost = restHost } + public fun endpoint(endpoint: String): ClientBuilder = apply { options.endpoint = endpoint } /** * Sets [ClientOptions.port]. @@ -232,14 +232,6 @@ public object PubSubDevice { */ public fun proxy(proxy: ProxyOptions): ClientBuilder = apply { options.proxy = proxy } - /** - * Sets [ClientOptions.environment]. - * - * @param environment the value to set. - * @return this builder. - */ - public fun environment(environment: String): ClientBuilder = apply { options.environment = environment } - /** * Sets [ClientOptions.idempotentRestPublishing]. * @@ -344,14 +336,6 @@ public object PubSubDevice { */ public fun agents(agents: Map): ClientBuilder = apply { options.agents = agents } - /** - * Sets [ClientOptions.realtimeHost]. - * - * @param realtimeHost the value to set. - * @return this builder. - */ - public fun realtimeHost(realtimeHost: String): ClientBuilder = apply { options.realtimeHost = realtimeHost } - /** * Sets [ClientOptions.autoConnect]. * diff --git a/examples/src/main/kotlin/com/ably/example/MainActivity.kt b/examples/src/main/kotlin/com/ably/example/MainActivity.kt index acebebbf6..ffdbeda08 100644 --- a/examples/src/main/kotlin/com/ably/example/MainActivity.kt +++ b/examples/src/main/kotlin/com/ably/example/MainActivity.kt @@ -27,13 +27,13 @@ class MainActivity : ComponentActivity() { // A throwaway client that only signs a token; it never connects. PubSubDevice.clientBuilder() .key(apiKey) - .environment("sandbox") + .endpoint("nonprod:sandbox") .autoConnect(false) .build() .use { it.auth.requestToken(null, null) } } ) - environment("sandbox") + endpoint("nonprod:sandbox") } else { key(BuildConfig.ABLY_KEY) } diff --git a/examples/src/main/kotlin/com/ably/example/screen/MainScreen.kt b/examples/src/main/kotlin/com/ably/example/screen/MainScreen.kt index 3ac4db05d..a73a7d3ff 100644 --- a/examples/src/main/kotlin/com/ably/example/screen/MainScreen.kt +++ b/examples/src/main/kotlin/com/ably/example/screen/MainScreen.kt @@ -14,7 +14,7 @@ import io.ably.pubsub.realtime.PubSubRealtimeClient @Composable fun MainScreen(realtimeClient: PubSubRealtimeClient) { var selectedTab by remember { mutableIntStateOf(0) } - val isSandbox = realtimeClient.options.environment == "sandbox" + val isSandbox = realtimeClient.options.endpoint == "nonprod:sandbox" val tabs = listOf( TabItem("Color Voting", Icons.Default.Favorite), diff --git a/lib/src/main/java/io/ably/pubsub/debug/DebugOptions.java b/lib/src/main/java/io/ably/pubsub/debug/DebugOptions.java index 0e25fd9e4..32ebf5833 100644 --- a/lib/src/main/java/io/ably/pubsub/debug/DebugOptions.java +++ b/lib/src/main/java/io/ably/pubsub/debug/DebugOptions.java @@ -50,8 +50,7 @@ public DebugOptions copy() { copied.logLevel = logLevel; copied.logHandler = logHandler; copied.tls = tls; - copied.restHost = restHost; - copied.realtimeHost = realtimeHost; + copied.endpoint = endpoint; copied.port = port; copied.tlsPort = tlsPort; copied.autoConnect = autoConnect; @@ -60,7 +59,6 @@ public DebugOptions copy() { copied.echoMessages = echoMessages; copied.recover = recover; copied.proxy = proxy; - copied.environment = environment; copied.idempotentRestPublishing = idempotentRestPublishing; copied.httpOpenTimeout = httpOpenTimeout; copied.httpRequestTimeout = httpRequestTimeout; diff --git a/lib/src/main/java/io/ably/pubsub/http/HttpCore.java b/lib/src/main/java/io/ably/pubsub/http/HttpCore.java index a900a70ae..33d112ae7 100644 --- a/lib/src/main/java/io/ably/pubsub/http/HttpCore.java +++ b/lib/src/main/java/io/ably/pubsub/http/HttpCore.java @@ -88,7 +88,7 @@ public HttpCore(ClientOptions options, Auth auth, PlatformAgentProvider platform this.platformAgentProvider = platformAgentProvider; this.scheme = options.tls ? "https://" : "http://"; this.port = Defaults.getPort(options); - this.hosts = new Hosts(options.restHost, Defaults.HOST_REST, options); + this.hosts = new Hosts(options); ProxyOptions proxyOptions = options.proxy; if (proxyOptions != null) { String proxyHost = proxyOptions.host; diff --git a/lib/src/main/java/io/ably/pubsub/http/HttpUtils.java b/lib/src/main/java/io/ably/pubsub/http/HttpUtils.java index 97b65d819..61e775755 100644 --- a/lib/src/main/java/io/ably/pubsub/http/HttpUtils.java +++ b/lib/src/main/java/io/ably/pubsub/http/HttpUtils.java @@ -187,9 +187,20 @@ public static String encodeURIComponent(String str) { return builder.toString(); } + /** + * Formats a host for the authority component of a URL, bracketing an IPv6 literal + * (RFC 3986 section 3.2.2) so that its colons are not mistaken for the port separator. + * + * @param host a hostname, IPv4 address, or IPv6 address, bracketed or not + * @return the host, safe to place between a scheme and a port + */ + public static String hostForUrl(String host) { + return host.indexOf(':') >= 0 && !host.startsWith("[") ? '[' + host + ']' : host; + } + static URL buildURL(String scheme, String host, int port, String path, Param[] params) { StringBuilder builder = new StringBuilder(scheme) - .append(host) + .append(hostForUrl(host)) .append(':') .append(port) .append(HttpUtils.encodeParams(path, params)); diff --git a/lib/src/main/java/io/ably/pubsub/transport/ConnectionManager.java b/lib/src/main/java/io/ably/pubsub/transport/ConnectionManager.java index 665ec281f..835196ffb 100644 --- a/lib/src/main/java/io/ably/pubsub/transport/ConnectionManager.java +++ b/lib/src/main/java/io/ably/pubsub/transport/ConnectionManager.java @@ -791,7 +791,7 @@ public ConnectionManager(final PubSubRealtimeClient ably, final Connection conne this.liveObjectsPlugin = liveObjectsPlugin; ClientOptions options = ably.options; - this.hosts = new Hosts(options.realtimeHost, Defaults.HOST_REALTIME, options); + this.hosts = new Hosts(options); /* debug options */ ITransport.Factory transportFactory = null; diff --git a/lib/src/main/java/io/ably/pubsub/transport/Defaults.java b/lib/src/main/java/io/ably/pubsub/transport/Defaults.java index de50be07e..e22be2809 100644 --- a/lib/src/main/java/io/ably/pubsub/transport/Defaults.java +++ b/lib/src/main/java/io/ably/pubsub/transport/Defaults.java @@ -33,9 +33,10 @@ public class Defaults { public static final String ABLY_AGENT_HEADER = "Ably-Agent"; /* Hosts */ - public static final String[] HOST_FALLBACKS = { "A.ably-realtime.com", "B.ably-realtime.com", "C.ably-realtime.com", "D.ably-realtime.com", "E.ably-realtime.com" }; - public static final String HOST_REST = "rest.ably.io"; - public static final String HOST_REALTIME = "realtime.ably.io"; + /* REC1a: the endpoint used when ClientOptions#endpoint is unset */ + public static final String ENDPOINT = "main"; + /* REC2c1: the fallback domains for the default endpoint */ + public static final String[] HOST_FALLBACKS = getEndpointFallbackHosts(ENDPOINT); public static final int PORT = 80; public static final int TLS_PORT = 443; @@ -74,14 +75,68 @@ public static int getPort(ClientOptions options) { : ((options.port != 0) ? options.port : Defaults.PORT); } - /* Construct environment fallback hosts as per RSC15i */ - public static String[] getEnvironmentFallbackHosts(String environment) { + /** + * Resolves the primary domain that both REST requests and realtime connections use. + *

+ * Spec: REC1 + * + * @param endpoint the configured endpoint, or null for the default + * @return the primary domain + */ + public static String getPrimaryDomain(String endpoint) { + if (endpoint == null) { + endpoint = ENDPOINT; + } + /* REC1b2 */ + if (isHostname(endpoint)) { + return endpoint; + } + /* REC1b3 */ + if (endpoint.startsWith(NONPROD_PREFIX)) { + return endpoint.substring(NONPROD_PREFIX.length()) + ".realtime.ably-nonprod.net"; + } + /* REC1b4 */ + return endpoint + ".realtime.ably.net"; + } + + /** + * Resolves the default fallback domains for an endpoint, used when ClientOptions#fallbackHosts is unset. + *

+ * Spec: REC2c + * + * @param endpoint the configured endpoint, or null for the default + * @return the fallback domains; empty when the endpoint is a hostname + */ + public static String[] getEndpointFallbackHosts(String endpoint) { + if (endpoint == null) { + endpoint = ENDPOINT; + } + /* REC2c2 */ + if (isHostname(endpoint)) { + return new String[0]; + } + /* REC2c3 */ + if (endpoint.startsWith(NONPROD_PREFIX)) { + return fallbackHosts(endpoint.substring(NONPROD_PREFIX.length()), "ably-realtime-nonprod.com"); + } + /* REC2c1, REC2c4 */ + return fallbackHosts(endpoint, "ably-realtime.com"); + } + + private static final String NONPROD_PREFIX = "nonprod:"; + + /* REC1b2: an endpoint is a hostname if it contains '.' or '::', or is "localhost" */ + private static boolean isHostname(String endpoint) { + return endpoint.contains(".") || endpoint.contains("::") || endpoint.equals("localhost"); + } + + private static String[] fallbackHosts(String routingPolicyId, String domain) { return new String[] { - environment + "-a-fallback.ably-realtime.com", - environment + "-b-fallback.ably-realtime.com", - environment + "-c-fallback.ably-realtime.com", - environment + "-d-fallback.ably-realtime.com", - environment + "-e-fallback.ably-realtime.com" + routingPolicyId + ".a.fallback." + domain, + routingPolicyId + ".b.fallback." + domain, + routingPolicyId + ".c.fallback." + domain, + routingPolicyId + ".d.fallback." + domain, + routingPolicyId + ".e.fallback." + domain }; } } diff --git a/lib/src/main/java/io/ably/pubsub/transport/Hosts.java b/lib/src/main/java/io/ably/pubsub/transport/Hosts.java index 558afffd8..acb6644ff 100644 --- a/lib/src/main/java/io/ably/pubsub/transport/Hosts.java +++ b/lib/src/main/java/io/ably/pubsub/transport/Hosts.java @@ -1,8 +1,6 @@ package io.ably.pubsub.transport; -import io.ably.pubsub.types.AblyException; import io.ably.pubsub.types.ClientOptions; -import io.ably.pubsub.types.ErrorInfo; import io.ably.pubsub.util.Clock; import io.ably.pubsub.util.SystemClock; @@ -17,10 +15,7 @@ */ public class Hosts { private final String primaryHost; - private final boolean primaryHostIsDefault; - private final String defaultHost; private final String[] fallbackHosts; - private final boolean fallbackHostsIsDefault; private final long fallbackRetryTimeout; private final Preferred preferred = new Preferred(); @@ -29,43 +24,19 @@ public class Hosts { /** * Create Hosts object * - * @param primaryHost the primary hostname, null if not configured - * @param defaultHost the default hostname that the primary hostname must - * match for fallback to occur - * @param options ClientOptions to get environment and fallbackHosts from + * @param options ClientOptions to get endpoint and fallbackHosts from * - * The fallback and environment processing here is used when the Hosts - * object is used by a ConnectionManager (for a realtime connection) or by - * an HttpCore for a rest connection. The case where the Hosts object is used - * by an HttpCore that is being used by a ConnectionManager goes through this - * code, but the results are ignored because ConnectionManager then calls - * setHost() and fallback is not used. + * REST requests and realtime connections share the same primary domain (RSC25, RTN2), + * so an HttpCore and a ConnectionManager built from the same options resolve the same hosts. */ - public Hosts(final String primaryHost, final String defaultHost, final ClientOptions options) throws AblyException { - this.defaultHost = defaultHost; - boolean hasCustomPrimaryHost = primaryHost != null && !primaryHost.equalsIgnoreCase(defaultHost); - String[] tempFallbackHosts = options.fallbackHosts; - - boolean isProduction = options.environment == null || options.environment.isEmpty() || "production".equalsIgnoreCase(options.environment); - - if (!hasCustomPrimaryHost && tempFallbackHosts == null && options.port == 0 && options.tlsPort == 0) { - tempFallbackHosts = isProduction ? Defaults.HOST_FALLBACKS : Defaults.getEnvironmentFallbackHosts(options.environment); - } - - if (hasCustomPrimaryHost) { - this.primaryHost = primaryHost; - if (options.environment != null) { - /* TO3k2: It is never valid to provide both a restHost and environment value - * TO3k3: It is never valid to provide both a realtimeHost and environment value */ - throw AblyException.fromErrorInfo(new ErrorInfo("cannot set both restHost/realtimeHost and environment options", 40000, 400)); - } - } else { - this.primaryHost = isProduction ? defaultHost : options.environment + "-" + defaultHost; - } - primaryHostIsDefault = this.primaryHost.equalsIgnoreCase(defaultHost); - - fallbackHostsIsDefault = Arrays.equals(Defaults.HOST_FALLBACKS, tempFallbackHosts); - fallbackHosts = tempFallbackHosts == null ? new String[] {} : tempFallbackHosts.clone(); + public Hosts(final ClientOptions options) { + /* REC1 */ + this.primaryHost = Defaults.getPrimaryDomain(options.endpoint); + /* REC2a2: explicit fallbackHosts always replace the defaults; REC2c: otherwise derive them from the endpoint */ + String[] tempFallbackHosts = options.fallbackHosts != null + ? options.fallbackHosts + : Defaults.getEndpointFallbackHosts(options.endpoint); + fallbackHosts = tempFallbackHosts.clone(); /* RSC15a: shuffle the fallback hosts. */ Collections.shuffle(Arrays.asList(fallbackHosts)); fallbackRetryTimeout = options.fallbackRetryTimeout; @@ -111,14 +82,8 @@ public synchronized String getPreferredHost() { * null, if there is no successor fallback available. */ public synchronized String getFallback(String lastHost) { - if (fallbackHosts == null) - return null; int idx; if (lastHost.equals(primaryHost)) { - /* RSC15b, RTN17b: only use fallback if the hostname has not been overridden - * or if ClientOptions#fallbackHosts was provided. */ - if (!primaryHostIsDefault && fallbackHostsIsDefault) - return null; idx = 0; } else if(lastHost.equals(preferred.getHostOrClearIfExpired(clock))) { /* RSC15f: there was a failure on an unexpired, cached fallback; so try again using the primary */ @@ -139,9 +104,6 @@ public synchronized String getFallback(String lastHost) { } public synchronized int fallbackHostsRemaining(String candidateHost) { - if(fallbackHosts == null) { - return 0; - } if(candidateHost.equals(primaryHost) || candidateHost.equals(preferred.getHost())) { return fallbackHosts.length; } diff --git a/lib/src/main/java/io/ably/pubsub/transport/WebSocketTransport.java b/lib/src/main/java/io/ably/pubsub/transport/WebSocketTransport.java index 037c43c7f..87cb923e9 100644 --- a/lib/src/main/java/io/ably/pubsub/transport/WebSocketTransport.java +++ b/lib/src/main/java/io/ably/pubsub/transport/WebSocketTransport.java @@ -118,7 +118,7 @@ public void connect(ConnectListener connectListener) { try { boolean isTls = params.options.tls; String wsScheme = isTls ? "wss://" : "ws://"; - wsUri = wsScheme + params.host + ':' + params.port + "/"; + wsUri = wsScheme + HttpUtils.hostForUrl(params.host) + ':' + params.port + "/"; Param[] authParams = connectionManager.getAuthParams(); Param[] connectParams = params.getConnectParams(authParams); if (connectParams.length > 0) diff --git a/lib/src/main/java/io/ably/pubsub/types/ClientOptions.java b/lib/src/main/java/io/ably/pubsub/types/ClientOptions.java index 4cbfb4b68..1efa71418 100644 --- a/lib/src/main/java/io/ably/pubsub/types/ClientOptions.java +++ b/lib/src/main/java/io/ably/pubsub/types/ClientOptions.java @@ -73,20 +73,19 @@ public ClientOptions(String key) throws AblyException { public Map headers; /** - * Enables a non-default Ably host to be specified. For development environments only. - * The default value is rest.ably.io. + * Sets the endpoint that both REST requests and realtime connections use. The value can be: + *

    + *
  • a routing policy name, such as {@code main}, which connects to {@code [name].realtime.ably.net};
  • + *
  • a nonprod routing policy name prefixed with {@code nonprod:}, such as {@code nonprod:sandbox}, + * which connects to {@code [name].realtime.ably-nonprod.net};
  • + *
  • a hostname (one containing {@code .} or {@code ::}, or {@code localhost}), which is used as given + * with no default fallback hosts.
  • + *
+ * The default is {@code main}. *

- * Spec: RSC12, TO3k2 + * Spec: REC1 */ - public String restHost; - - /** - * Enables a non-default Ably host to be specified for realtime connections. - * For development environments only. The default value is realtime.ably.io. - *

- * Spec: RTC1d, TO3k3 - */ - public String realtimeHost; + public String endpoint; /** * Enables a non-default Ably port to be specified. For development environments only. The default value is 80. @@ -157,13 +156,6 @@ public ClientOptions(String key) throws AblyException { */ public ProxyOptions proxy; - /** - * Enables a custom environment to be used with the Ably service. - *

- * Spec: RSC15b, TO3k1 - */ - public String environment; - /** * When true, enables idempotent publishing by assigning a unique message ID client-side, * allowing the Ably servers to discard automatic publish retries following a failure such as a network fault. @@ -228,8 +220,10 @@ public ClientOptions(String key) throws AblyException { /** * An array of fallback hosts to be used in the case of an error necessitating the use of an alternative host. * If you have been provided a set of custom fallback hosts by Ably, please specify them here. + * When set, these replace the default fallback hosts derived from {@link #endpoint}; + * an empty array disables fallback. *

- * Spec: RSC15b, RSC15a, TO3k6 + * Spec: REC2a2, RSC15a, TO3k6 */ public String[] fallbackHosts; @@ -326,8 +320,7 @@ public ClientOptions copy() { copied.logLevel = logLevel; copied.logHandler = logHandler; copied.tls = tls; - copied.restHost = restHost; - copied.realtimeHost = realtimeHost; + copied.endpoint = endpoint; copied.port = port; copied.tlsPort = tlsPort; copied.autoConnect = autoConnect; @@ -336,7 +329,6 @@ public ClientOptions copy() { copied.echoMessages = echoMessages; copied.recover = recover; copied.proxy = proxy; - copied.environment = environment; copied.idempotentRestPublishing = idempotentRestPublishing; copied.httpOpenTimeout = httpOpenTimeout; copied.httpRequestTimeout = httpRequestTimeout; diff --git a/lib/src/test/java/io/ably/pubsub/http/HttpUtilsTest.java b/lib/src/test/java/io/ably/pubsub/http/HttpUtilsTest.java new file mode 100644 index 000000000..631f20443 --- /dev/null +++ b/lib/src/test/java/io/ably/pubsub/http/HttpUtilsTest.java @@ -0,0 +1,32 @@ +package io.ably.pubsub.http; + +import org.junit.Test; + +import java.net.URL; + +import static org.junit.Assert.assertEquals; + +public class HttpUtilsTest { + + @Test + public void hostForUrl_ipv6Literal_isBracketed() { + assertEquals("[::1]", HttpUtils.hostForUrl("::1")); + assertEquals("[2001:db8::1]", HttpUtils.hostForUrl("2001:db8::1")); + } + + @Test + public void hostForUrl_otherHosts_areUnchanged() { + assertEquals("[::1]", HttpUtils.hostForUrl("[::1]")); + assertEquals("127.0.0.1", HttpUtils.hostForUrl("127.0.0.1")); + assertEquals("localhost", HttpUtils.hostForUrl("localhost")); + assertEquals("main.realtime.ably.net", HttpUtils.hostForUrl("main.realtime.ably.net")); + } + + @Test + public void buildURL_ipv6Host_addressesHostAndPort() { + URL url = HttpUtils.buildURL("http://", "::1", 8080, "/time", null); + assertEquals("http://[::1]:8080/time", url.toString()); + assertEquals("[::1]", url.getHost()); + assertEquals(8080, url.getPort()); + } +} diff --git a/lib/src/test/java/io/ably/pubsub/test/common/Setup.java b/lib/src/test/java/io/ably/pubsub/test/common/Setup.java index 89172096e..9cc6a4347 100644 --- a/lib/src/test/java/io/ably/pubsub/test/common/Setup.java +++ b/lib/src/test/java/io/ably/pubsub/test/common/Setup.java @@ -120,9 +120,7 @@ public static TestParameters getDefault() { } public static class TestVars extends AppSpec { - public String restHost; - public String realtimeHost; - public String environment; + public String endpoint; public int port; public int tlsPort; public boolean tls; @@ -156,9 +154,7 @@ public void fillInOptions(ClientOptions opts) { public void fillInOptions(ClientOptions opts, TestParameters params) { if(params == null) { params = TestParameters.getDefault(); } opts.useBinaryProtocol = params.useBinaryProtocol; - opts.restHost = restHost; - opts.realtimeHost = realtimeHost; - opts.environment = environment; + opts.endpoint = endpoint; opts.port = port; opts.tlsPort = tlsPort; opts.tls = tls; @@ -171,22 +167,15 @@ public static synchronized TestVars getTestVars() { private static TestVars __getTestVars() { if(testVars == null) { - host = argumentLoader.getTestArgument("ABLY_REST_HOST"); - environment = argumentLoader.getTestArgument("ABLY_ENV"); - if(environment == null) { - environment = "sandbox"; - } - - if(host != null) { - wsHost = argumentLoader.getTestArgument("ABLY_REALTIME_HOST"); - if(wsHost == null) - wsHost = host; + endpoint = argumentLoader.getTestArgument("ABLY_ENDPOINT"); + if(endpoint == null) { + endpoint = "nonprod:sandbox"; } if(argumentLoader.getTestArgument("ABLY_PORT") != null) { port = Integer.valueOf(argumentLoader.getTestArgument("ABLY_PORT")); tlsPort = Integer.valueOf(argumentLoader.getTestArgument("ABLY_TLS_PORT")); - } else if((host != null && host.contains("local")) || environment.equals("local")) { + } else if(endpoint.contains("local")) { port = 8080; tlsPort = 8081; } else { @@ -202,8 +191,7 @@ private static TestVars __getTestVars() { * but we are only instancing the library to use the http * convenience methods */ opts.key = "none:none"; - opts.restHost = host; - opts.environment = environment; + opts.endpoint = endpoint; opts.port = port; opts.tlsPort = tlsPort; opts.tls = true; @@ -229,9 +217,7 @@ public TestVars handleResponse(HttpCore.Response response, ErrorInfo error) thro } TestVars result = (TestVars)Serialisation.gson.fromJson(new String(response.body), TestVars.class); - result.restHost = host; - result.realtimeHost = wsHost; - result.environment = environment; + result.endpoint = endpoint; result.port = port; result.tlsPort = tlsPort; result.tls = true; @@ -253,8 +239,7 @@ private static void __clearTestVars() { if(testVars != null) { try { ClientOptions opts = new ClientOptions(testVars.keys[0].keyStr); - opts.restHost = host; - opts.environment = environment; + opts.endpoint = endpoint; opts.port = port; opts.tlsPort = tlsPort; opts.tls = true; @@ -282,9 +267,7 @@ public void execute(HttpScheduler http, Callback callback) throws AblyExce private static final String specFile = "local/testAppSpec.json"; private static PubSubHttpClient ably; - private static String environment; - private static String host; - private static String wsHost; + private static String endpoint; private static int port; private static int tlsPort; diff --git a/lib/src/test/java/io/ably/pubsub/test/http/HttpAuthTest.java b/lib/src/test/java/io/ably/pubsub/test/http/HttpAuthTest.java index 758a785bb..4b9052816 100644 --- a/lib/src/test/java/io/ably/pubsub/test/http/HttpAuthTest.java +++ b/lib/src/test/java/io/ably/pubsub/test/http/HttpAuthTest.java @@ -175,8 +175,7 @@ public void authinit1() { public void authinit2() { try { ClientOptions opts = createOptions(); - opts.restHost = testVars.restHost; - opts.environment = testVars.environment; + opts.endpoint = testVars.endpoint; opts.port = testVars.port; opts.tlsPort = testVars.tlsPort; opts.tls = testVars.tls; @@ -329,7 +328,7 @@ public void authinit4() { assertNotNull("Expected token value", tokenDetails.token); ClientOptions opts = new ClientOptions(); opts.token = tokenDetails.token; - opts.environment = testVars.environment; + opts.endpoint = testVars.endpoint; PubSubHttpClient ably = HttpClientFactory.create(opts); assertEquals("Unexpected Auth method mismatch", ably.auth.getAuthMethod(), AuthMethod.token); assertEquals("Unexpected clientId mismatch", ably.auth.clientId, null); @@ -347,7 +346,7 @@ public void authinit4() { public void auth_authURL_tokenrequest() { try { ClientOptions opts = createOptions(); - opts.environment = testVars.environment; + opts.endpoint = testVars.endpoint; opts.authUrl = "http://localhost:8982/get-token-request"; PubSubHttpClient ably = HttpClientFactory.create(opts); /* make a call to trigger token request */ @@ -372,7 +371,7 @@ public void auth_authURL_tokenrequest() { public void auth_authURL_tokenrequest_post() { try { ClientOptions opts = createOptions(); - opts.environment = testVars.environment; + opts.endpoint = testVars.endpoint; opts.authUrl = "http://localhost:8982/post-token-request"; opts.authMethod = HttpConstants.Methods.POST; PubSubHttpClient ably = HttpClientFactory.create(opts); @@ -399,7 +398,7 @@ public void auth_authURL_tokenrequest_post() { public void auth_authURL_token() { try { ClientOptions opts = createOptions(); - opts.environment = testVars.environment; + opts.endpoint = testVars.endpoint; opts.authUrl = "http://localhost:8982/get-token"; PubSubHttpClient ably = HttpClientFactory.create(opts); /* make a call to trigger token request */ @@ -424,7 +423,7 @@ public void auth_authURL_token() { public void auth_authURL_err() { try { ClientOptions opts = createOptions(); - opts.environment = testVars.environment; + opts.endpoint = testVars.endpoint; opts.authUrl = "http://localhost:8982/404"; PubSubHttpClient ably = HttpClientFactory.create(opts); /* make a call to trigger token request */ @@ -449,7 +448,7 @@ public void auth_authURL_err() { public void auth_authURL_timeout() { try { ClientOptions opts = createOptions(); - opts.environment = testVars.environment; + opts.endpoint = testVars.endpoint; opts.authUrl = "http://localhost:8982/wait?delay=6000"; opts.httpRequestTimeout = 5000; PubSubHttpClient ably = HttpClientFactory.create(opts); @@ -1631,7 +1630,7 @@ public void auth_testQueryTime() { nanoHTTPD.clearRequestHistory(); ClientOptions opts = new ClientOptions(testVars.keys[0].keyStr); opts.tls = false; - opts.restHost = "localhost"; + opts.endpoint = "localhost"; opts.port = nanoHTTPD.getListeningPort(); opts.queryTime = true; diff --git a/lib/src/test/java/io/ably/pubsub/test/http/HttpClientTest.java b/lib/src/test/java/io/ably/pubsub/test/http/HttpClientTest.java index decac912b..02767973a 100644 --- a/lib/src/test/java/io/ably/pubsub/test/http/HttpClientTest.java +++ b/lib/src/test/java/io/ably/pubsub/test/http/HttpClientTest.java @@ -63,8 +63,7 @@ public void error_info_contains_request_id() throws AblyException { Helpers.RawHttpTracker httpListener = new Helpers.RawHttpTracker(); opts.httpListener = httpListener; opts.addRequestIds = true; - opts.environment = null; - opts.restHost = ""; + opts.endpoint = "invalid-host1.com"; opts.fallbackHosts = new String[]{"ably.com"}; PubSubHttpClient ably = HttpClientFactory.create(opts); @@ -90,8 +89,7 @@ public void request_id_remain_same_retried_fallbacks() throws AblyException { Helpers.RawHttpTracker httpListener = new Helpers.RawHttpTracker(); opts.httpListener = httpListener; opts.addRequestIds = true; - opts.environment = null; - opts.restHost = "invalid-host1.com"; + opts.endpoint = "invalid-host1.com"; opts.fallbackHosts = new String[]{"invalid-host2.com", "invalid-host3.com"}; PubSubHttpClient ably = HttpClientFactory.create(opts); diff --git a/lib/src/test/java/io/ably/pubsub/test/http/HttpErrorTest.java b/lib/src/test/java/io/ably/pubsub/test/http/HttpErrorTest.java index 155342418..8ce48dc23 100644 --- a/lib/src/test/java/io/ably/pubsub/test/http/HttpErrorTest.java +++ b/lib/src/test/java/io/ably/pubsub/test/http/HttpErrorTest.java @@ -53,10 +53,9 @@ public void errorHrefWhenPresentInHref() { final Vector logMessages = new Vector(); try { ClientOptions opts = createOptions(testVars.keys[0].keyStr); - opts.environment = null; opts.tls = false; opts.port = server.getListeningPort(); - opts.restHost = "localhost"; + opts.endpoint = "localhost"; opts.logHandler = new Log.LogHandler() { @Override public void println(int severity, String tag, String msg, Throwable tr) { @@ -82,10 +81,9 @@ public void errorHrefWhenPresentInMessage() { final Vector logMessages = new Vector(); try { ClientOptions opts = createOptions(testVars.keys[0].keyStr); - opts.environment = null; opts.tls = false; opts.port = server.getListeningPort(); - opts.restHost = "localhost"; + opts.endpoint = "localhost"; opts.logHandler = new Log.LogHandler() { @Override public void println(int severity, String tag, String msg, Throwable tr) { @@ -111,10 +109,9 @@ public void errorHrefWhenCodePresent() { final Vector logMessages = new Vector(); try { ClientOptions opts = createOptions(testVars.keys[0].keyStr); - opts.environment = null; opts.tls = false; opts.port = server.getListeningPort(); - opts.restHost = "localhost"; + opts.endpoint = "localhost"; opts.logHandler = new Log.LogHandler() { @Override public void println(int severity, String tag, String msg, Throwable tr) { diff --git a/lib/src/test/java/io/ably/pubsub/test/http/HttpHeaderTest.java b/lib/src/test/java/io/ably/pubsub/test/http/HttpHeaderTest.java index 7d8299810..97cbce1b1 100644 --- a/lib/src/test/java/io/ably/pubsub/test/http/HttpHeaderTest.java +++ b/lib/src/test/java/io/ably/pubsub/test/http/HttpHeaderTest.java @@ -59,10 +59,9 @@ public void header_lib_channel_publish() { try { /* Init values for local server */ ClientOptions opts = createOptions(testVars.keys[0].keyStr); - opts.environment = null; opts.tls = false; opts.port = server.getListeningPort(); - opts.restHost = "localhost"; + opts.endpoint = "localhost"; PubSubHttpClient ably = HttpClientFactory.create(opts); /* Publish message */ @@ -105,10 +104,9 @@ public void header_client_id_on_channel_publish() { try { /* Init values for local server */ ClientOptions opts = createOptions(testVars.keys[0].keyStr); - opts.environment = null; opts.tls = false; opts.port = server.getListeningPort(); - opts.restHost = "localhost"; + opts.endpoint = "localhost"; opts.clientId = "test client"; PubSubHttpClient ably = HttpClientFactory.create(opts); diff --git a/lib/src/test/java/io/ably/pubsub/test/http/HttpInitTest.java b/lib/src/test/java/io/ably/pubsub/test/http/HttpInitTest.java index e352454f4..34587c5b0 100644 --- a/lib/src/test/java/io/ably/pubsub/test/http/HttpInitTest.java +++ b/lib/src/test/java/io/ably/pubsub/test/http/HttpInitTest.java @@ -7,7 +7,6 @@ import java.io.ByteArrayOutputStream; import java.io.PrintStream; -import java.util.Locale; import io.ably.pubsub.http.HttpClientFactory; import io.ably.pubsub.http.PubSubHttpClient; @@ -109,7 +108,8 @@ public void init_no_auth() { } /** - * Init library with specified host + * Init library with specified hostname endpoint + * Spec: REC1b2 */ @Test public void init_host() { @@ -118,9 +118,9 @@ public void init_host() { TestVars testVars = Setup.getTestVars(); ClientOptions opts = new ClientOptions(testVars.keys[0].keyStr); - opts.restHost = hostExpected; + opts.endpoint = hostExpected; PubSubHttpClient ably = HttpClientFactory.create(opts); - assertEquals("Unexpected host mismatch", hostExpected, ably.options.restHost); + assertEquals("Unexpected host mismatch", hostExpected, ably.httpCore.getPrimaryHost()); } catch (AblyException e) { e.printStackTrace(); fail("init4: Unexpected exception instantiating library"); @@ -272,17 +272,16 @@ public void init_default_log_output_stream() { } /** - * Init library with 'production' environment - * Spec: RSC11 + * Init library with no endpoint + * Spec: REC1a */ @Test - public void init_production_environment() { + public void init_default_endpoint() { try { TestVars testVars = Setup.getTestVars(); ClientOptions opts = new ClientOptions(testVars.keys[0].keyStr); - opts.environment = "production"; PubSubHttpClient ably = HttpClientFactory.create(opts); - assertEquals("Unexpected host mismatch", Defaults.HOST_REST, ably.httpCore.getPrimaryHost()); + assertEquals("Unexpected host mismatch", "main.realtime.ably.net", ably.httpCore.getPrimaryHost()); } catch (AblyException e) { e.printStackTrace(); fail("init4: Unexpected exception instantiating library"); @@ -290,18 +289,17 @@ public void init_production_environment() { } /** - * Init library with given environment - * Spec: RSC11 + * Init library with given routing policy endpoint + * Spec: REC1b4 */ @Test - public void init_given_environment() { - final String givenEnvironment = "staging"; + public void init_given_endpoint() { try { TestVars testVars = Setup.getTestVars(); ClientOptions opts = new ClientOptions(testVars.keys[0].keyStr); - opts.environment = givenEnvironment; + opts.endpoint = "acme"; PubSubHttpClient ably = HttpClientFactory.create(opts); - assertEquals("Unexpected host mismatch", String.format(Locale.ROOT, "%s-%s", givenEnvironment, Defaults.HOST_REST), ably.httpCore.getPrimaryHost()); + assertEquals("Unexpected host mismatch", "acme.realtime.ably.net", ably.httpCore.getPrimaryHost()); } catch (AblyException e) { e.printStackTrace(); fail("init4: Unexpected exception instantiating library"); @@ -309,23 +307,20 @@ public void init_given_environment() { } /** - * Init library with given environment and specified host - * Spec: RSC11 + * Init library with given nonprod routing policy endpoint + * Spec: REC1b3 */ @Test - public void init_given_host_environment() { - final String givenEnvironment = "staging"; - final String specifiedHost = "fake.ably.io"; + public void init_given_nonprod_endpoint() { try { TestVars testVars = Setup.getTestVars(); ClientOptions opts = new ClientOptions(testVars.keys[0].keyStr); - opts.restHost = specifiedHost; - opts.environment = givenEnvironment; + opts.endpoint = "nonprod:sandbox"; PubSubHttpClient ably = HttpClientFactory.create(opts); - fail("init4: Expected exception instantiating library"); - assertEquals("Unexpected host mismatch", specifiedHost, ably.options.restHost); + assertEquals("Unexpected host mismatch", "sandbox.realtime.ably-nonprod.net", ably.httpCore.getPrimaryHost()); } catch (AblyException e) { - /* pass: Got exception from setting restHost and environment */ + e.printStackTrace(); + fail("init4: Unexpected exception instantiating library"); } } } diff --git a/lib/src/test/java/io/ably/pubsub/test/http/HttpJWTTest.java b/lib/src/test/java/io/ably/pubsub/test/http/HttpJWTTest.java index 9783b19e7..15f4b89bb 100644 --- a/lib/src/test/java/io/ably/pubsub/test/http/HttpJWTTest.java +++ b/lib/src/test/java/io/ably/pubsub/test/http/HttpJWTTest.java @@ -25,7 +25,7 @@ public class HttpJWTTest extends ParameterizedTest { private Key key = testVars.keys[0]; - Param[] environment = new Param[]{ new Param("environment", testVars.environment) }; + Param[] environment = new Param[]{ new Param("environment", testVars.endpoint.replace("nonprod:", "")) }; Param[] validKeys = new Param[]{ new Param("keyName", key.keyName), new Param("keySecret", key.keySecret) }; Param[] invalidKeys = new Param[]{ new Param("keyName", key.keyName), new Param("keySecret", "invalidinvalid") }; Param[] tokenEmbedded = new Param[]{ new Param("jwtType", "embedded") }; diff --git a/lib/src/test/java/io/ably/pubsub/test/http/HttpProxyTest.java b/lib/src/test/java/io/ably/pubsub/test/http/HttpProxyTest.java index 464714930..e59c91dc6 100644 --- a/lib/src/test/java/io/ably/pubsub/test/http/HttpProxyTest.java +++ b/lib/src/test/java/io/ably/pubsub/test/http/HttpProxyTest.java @@ -56,6 +56,8 @@ public void proxy_simple_invalid_port() { host = "sandbox-proxy.ably.io"; port = 6127; }}; + /* each attempt waits out the connect timeout, so retrying the fallbacks would outlast the test timeout */ + opts.fallbackHosts = new String[0]; PubSubHttpClient ably = HttpClientFactory.create(opts); /* attempt the call, expecting no exception */ diff --git a/lib/src/test/java/io/ably/pubsub/test/http/HttpRequestTest.java b/lib/src/test/java/io/ably/pubsub/test/http/HttpRequestTest.java index 6ba19bfa5..ff40e0d34 100644 --- a/lib/src/test/java/io/ably/pubsub/test/http/HttpRequestTest.java +++ b/lib/src/test/java/io/ably/pubsub/test/http/HttpRequestTest.java @@ -677,7 +677,7 @@ public void request_500() { try { DebugOptions opts = new DebugOptions(testVars.keys[0].keyStr); fillInOptions(opts); - opts.environment = "non.existent.env"; + opts.endpoint = "non.existent.env"; PubSubHttpClient ably = HttpClientFactory.create(opts); ably.request(HttpConstants.Methods.GET, "/", null, null, null); @@ -698,7 +698,7 @@ public void request_500_async() { final Waiter waiter = new Waiter(); DebugOptions opts = new DebugOptions(testVars.keys[0].keyStr); fillInOptions(opts); - opts.environment = "non.existent.env"; + opts.endpoint = "non.existent.env"; PubSubHttpClient ably = HttpClientFactory.create(opts); ably.requestAsync(HttpConstants.Methods.GET, "/", null, null, null, new AsyncHttpPaginatedResponse.Callback() { diff --git a/lib/src/test/java/io/ably/pubsub/test/http/HttpTest.java b/lib/src/test/java/io/ably/pubsub/test/http/HttpTest.java index 073ae45c0..9a26fc7fb 100644 --- a/lib/src/test/java/io/ably/pubsub/test/http/HttpTest.java +++ b/lib/src/test/java/io/ably/pubsub/test/http/HttpTest.java @@ -13,7 +13,6 @@ import io.ably.pubsub.test.util.EmptyPlatformAgentProvider; import io.ably.pubsub.test.util.StatusHandler; import io.ably.pubsub.test.util.TimeHandler; -import io.ably.pubsub.transport.Defaults; import io.ably.pubsub.types.AblyException; import io.ably.pubsub.types.Callback; import io.ably.pubsub.types.ClientOptions; @@ -76,7 +75,8 @@ */ public class HttpTest { - private static final String PATTERN_HOST_FALLBACK = "(?i)[a-e]\\.ably-realtime.com"; + private static final String DEFAULT_PRIMARY_HOST = "main.realtime.ably.net"; + private static final String PATTERN_HOST_FALLBACK = "(?i)main\\.[a-e]\\.fallback\\.ably-realtime\\.com"; private static final String CUSTOM_PATTERN_HOST_FALLBACK = "(?i)[f-k]\\.ably-realtime.com"; private static final String[] CUSTOM_HOSTS = { "f.ably-realtime.com", "g.ably-realtime.com", "h.ably-realtime.com", "i.ably-realtime.com", "j.ably-realtime.com", "k.ably-realtime.com" }; private static final String TEST_SERVER_HOST = "localhost"; @@ -187,7 +187,7 @@ public HttpCore setUrlArgumentStack(List urlArgumentStack) { */ int expectedCallCount = options.httpMaxRetryCount + 1; assertThat(urlHostArgumentStack.size(), is(equalTo(expectedCallCount))); - assertThat(urlHostArgumentStack.get(0), is(equalTo(Defaults.HOST_REST))); + assertThat(urlHostArgumentStack.get(0), is(equalTo(DEFAULT_PRIMARY_HOST))); for (int i = 1; i < expectedCallCount; i++) { urlHostArgumentStack.get(i).matches(PATTERN_HOST_FALLBACK); @@ -195,7 +195,7 @@ public HttpCore setUrlArgumentStack(List urlArgumentStack) { } /** - * Validates that fallbacks are disabled when ClientOptions#fallbackHosts are set to empty and the only host used is Defaults#HOST_REST + * Validates that fallbacks are disabled when ClientOptions#fallbackHosts are set to empty and the only host used is the default primary host * @throws AblyException */ @@ -243,9 +243,9 @@ public HttpCore setUrlArgumentStack(List urlArgumentStack) { assertTrue(false); } - /* Validate that only one host is used and it is Defaults#HOST_REST */ + /* Validate that only one host is used and it is the default primary host */ Assert.assertTrue(urlHostArgumentStack.size() == 1); - assertThat(urlHostArgumentStack.get(0), is(equalTo(Defaults.HOST_REST))); + assertThat(urlHostArgumentStack.get(0), is(equalTo(DEFAULT_PRIMARY_HOST))); } /** @@ -302,7 +302,7 @@ public void http_ably_execute_first_attempt_to_default() throws AblyException { false /* Ignore */ ); - assertThat("Unexpected default primary host", url.getAllValues().get(0).getHost(), is(equalTo(Defaults.HOST_REST))); + assertThat("Unexpected default primary host", url.getAllValues().get(0).getHost(), is(equalTo(DEFAULT_PRIMARY_HOST))); assertThat("Unexpected host fallback", url.getAllValues().get(1).getHost().matches(hostExpectedPattern), is(true)); assertThat("Unexpected response", responseActual, is(equalTo(responseExpected))); @@ -332,7 +332,7 @@ public void http_ably_execute_first_attempt_to_default() throws AblyException { anyBoolean(), /* Ignore */ any(HttpCore.ResponseHandler.class) /* Ignore */ ); - assertThat("Unexpected default primary host", url.getAllValues().get(2).getHost(), is(equalTo(Defaults.HOST_REST))); + assertThat("Unexpected default primary host", url.getAllValues().get(2).getHost(), is(equalTo(DEFAULT_PRIMARY_HOST))); assertThat("Unexpected response", responseActual2, is(equalTo(responseExpected))); } @@ -352,7 +352,7 @@ public void http_ably_execute_first_attempt_to_default() throws AblyException { public void http_ably_execute_overriden_host() throws AblyException { final String fakeHost = "fake.ably.io"; ClientOptions options = new ClientOptions("not:a.key"); - options.restHost = fakeHost; + options.endpoint = fakeHost; PubSubHttpClient ably = HttpClientFactory.create(options); HttpCore httpCore = Mockito.spy(new HttpCore(ably.options, ably.auth, platformAgentProvider)); @@ -500,7 +500,7 @@ public void http_ably_execute_empty_fallback_array() throws AblyException { anyBoolean(), /* Ignore */ any(HttpCore.ResponseHandler.class) /* Ignore */ ); - assertThat("Unexpected host", url.getAllValues().get(0).getHost(), is(equalTo(Defaults.HOST_REST))); + assertThat("Unexpected host", url.getAllValues().get(0).getHost(), is(equalTo(DEFAULT_PRIMARY_HOST))); } /** @@ -576,7 +576,7 @@ public void http_ably_execute_custom_fallback_array() throws AblyException { * - other calls executed against a random custom fallback host */ List allValues = url.getAllValues(); assertThat("Unexpected response", responseActual, is(equalTo(responseExpected))); - assertThat("Unexpected default primary host", allValues.get(0).getHost(), is(equalTo(Defaults.HOST_REST))); + assertThat("Unexpected default primary host", allValues.get(0).getHost(), is(equalTo(DEFAULT_PRIMARY_HOST))); for (int i = 1; i < allValues.size(); i++) { assertThat("Unexpected host fallback", fallbackHostsList.contains(allValues.get(i).getHost()), is(true)); } @@ -604,7 +604,7 @@ public void http_ably_execute_custom_fallback() throws AblyException { public T httpExecuteWithRetry(URL url, String method, Param[] headers, RequestBody requestBody, ResponseHandler responseHandler, boolean allowAblyAuth) throws AblyException { urlArgumentStack.add(url.getHost()); /* verify if fallback hosts are from specified list */ - if(!url.getHost().equals(Defaults.HOST_REST)) + if(!url.getHost().equals(DEFAULT_PRIMARY_HOST)) assertTrue(Arrays.asList(CUSTOM_HOSTS).contains(url.getHost())); return super.httpExecuteWithRetry(url, method, headers, requestBody, responseHandler, allowAblyAuth); @@ -636,7 +636,7 @@ public HttpCore setUrlArgumentStack(List urlArgumentStack) { int expectedCallCount = options.httpMaxRetryCount + 1; Assert.assertTrue(urlHostArgumentStack.size() == expectedCallCount); - assertThat(urlHostArgumentStack.get(0), is(equalTo(Defaults.HOST_REST))); + assertThat(urlHostArgumentStack.get(0), is(equalTo(DEFAULT_PRIMARY_HOST))); for (int i = 1; i < expectedCallCount; i++) { Assert.assertTrue(urlHostArgumentStack.get(i).matches(CUSTOM_PATTERN_HOST_FALLBACK)); @@ -659,7 +659,7 @@ public void http_execute_nofallback() throws Exception { HttpCore httpCore = Mockito.spy(new HttpCore(new ClientOptions(), null, platformAgentProvider)); String responseExpected = "Lorem Ipsum"; - String hostExpected = Defaults.HOST_REST; + String hostExpected = DEFAULT_PRIMARY_HOST; ArgumentCaptor url = ArgumentCaptor.forClass(URL.class); /* Partially mock httpCore */ @@ -866,7 +866,7 @@ public void http_execute_fallback_success_timeout_unexpired() throws Exception { opts.fallbackRetryTimeout = 2000L; HttpCore httpCore = Mockito.spy(new HttpCore(opts, null, platformAgentProvider)); - String hostExpected = Defaults.HOST_REST; + String hostExpected = DEFAULT_PRIMARY_HOST; ArgumentCaptor url = ArgumentCaptor.forClass(URL.class); /* Partially mock httpCore */ @@ -951,7 +951,7 @@ public void http_execute_fallback_failure_timeout_unexpired() throws Exception { opts.fallbackRetryTimeout = 2000L; HttpCore httpCore = Mockito.spy(new HttpCore(opts, null, platformAgentProvider)); - String primaryHost = Defaults.HOST_REST; + String primaryHost = DEFAULT_PRIMARY_HOST; ArgumentCaptor url = ArgumentCaptor.forClass(URL.class); /* Partially mock httpCore */ @@ -1040,7 +1040,7 @@ public void http_execute_fallback_timeout_expired() throws Exception { opts.fallbackRetryTimeout = 2000L; HttpCore httpCore = Mockito.spy(new HttpCore(opts, null, platformAgentProvider)); - String hostExpected = Defaults.HOST_REST; + String hostExpected = DEFAULT_PRIMARY_HOST; ArgumentCaptor url = ArgumentCaptor.forClass(URL.class); /* Partially mock httpCore */ @@ -1281,7 +1281,7 @@ private void test_asynchttp_concurrent(int poolSize, int requestCount, int expec try { ClientOptions options = new ClientOptions("not.a:key"); options.tls = false; - options.restHost = TEST_SERVER_HOST; + options.endpoint = TEST_SERVER_HOST; options.port = TEST_SERVER_PORT; if(poolSize > 0) { options.asyncHttpThreadpoolSize = poolSize; @@ -1416,7 +1416,7 @@ public void http_fallback_late_success_does_not_repin_expired_timeout() throws E final AtomicInteger requestCount = new AtomicInteger(0); DebugOptions opts = new DebugOptions("appId.keyId:keySecret"); - opts.restHost = primaryHost; + opts.endpoint = primaryHost; opts.fallbackHosts = new String[]{fallbackHost}; opts.fallbackRetryTimeout = 100L; opts.httpMaxRetryCount = 1; diff --git a/lib/src/test/java/io/ably/pubsub/test/http/HttpTimeTest.java b/lib/src/test/java/io/ably/pubsub/test/http/HttpTimeTest.java index 30b08ca77..f3000cce5 100644 --- a/lib/src/test/java/io/ably/pubsub/test/http/HttpTimeTest.java +++ b/lib/src/test/java/io/ably/pubsub/test/http/HttpTimeTest.java @@ -48,14 +48,13 @@ public void time1() { } /** - * Verify time fails without valid restHost + * Verify time fails without valid endpoint */ @Test public void time2() { try { ClientOptions opts = createOptions("not:a.key"); - opts.environment = null; - opts.restHost = "this.restHost.does.not.exist"; + opts.endpoint = "this.endpoint.does.not.exist"; PubSubHttpClient ably = HttpClientFactory.create(opts); ably.time(); fail("time2: Unexpected success getting time"); diff --git a/lib/src/test/java/io/ably/pubsub/test/realtime/ConnectionManagerTest.java b/lib/src/test/java/io/ably/pubsub/test/realtime/ConnectionManagerTest.java index e46f8ffd8..325ffd373 100644 --- a/lib/src/test/java/io/ably/pubsub/test/realtime/ConnectionManagerTest.java +++ b/lib/src/test/java/io/ably/pubsub/test/realtime/ConnectionManagerTest.java @@ -81,7 +81,7 @@ public void connectionmanager_fallback_none() throws AblyException { * - connectionManager is connected to the host without any fallback */ assertThat(connectionManager.getConnectionState().state, is(ConnectionState.connected)); - assertThat(connectionManager.getHost(), is(equalTo(opts.environment + "-realtime.ably.io"))); + assertThat(connectionManager.getHost(), is(equalTo(Defaults.getPrimaryDomain(opts.endpoint)))); } } @@ -99,8 +99,7 @@ public void connectionmanager_fallback_none() throws AblyException { @Test public void connectionmanager_fallback_none_customhost() throws AblyException { ClientOptions opts = createOptions(testVars.keys[0].keyStr); - opts.realtimeHost = "un.reachable.host.example.com"; - opts.environment = null; + opts.endpoint = "un.reachable.host.example.com"; try(PubSubRealtimeClient ably = RealtimeClientFactory.create(opts)) { ConnectionManager connectionManager = ably.connection.connectionManager; @@ -111,7 +110,7 @@ public void connectionmanager_fallback_none_customhost() throws AblyException { * - connectionManager's last host did not have any fallback */ assertThat(connectionManager.getConnectionState().state, is(ConnectionState.disconnected)); - assertThat(connectionManager.getHost(), is(equalTo(opts.realtimeHost))); + assertThat(connectionManager.getHost(), is(equalTo(opts.endpoint))); } } @@ -131,8 +130,7 @@ public void connectionmanager_fallback_none_customhost() throws AblyException { @Test public void connectionmanager_fallback_none_withoutconnection() throws AblyException { ClientOptions opts = createOptions(testVars.keys[0].keyStr); - opts.realtimeHost = "un.reachable.host"; - opts.environment = null; + opts.endpoint = "un.reachable.host"; opts.autoConnect = false; try(PubSubRealtimeClient ably = RealtimeClientFactory.create(opts)) { Connection connection = Mockito.mock(Connection.class); @@ -154,7 +152,7 @@ protected boolean checkConnectivity() { * - connectionManager did not apply any fallback behavior */ assertThat(connectionManager.getConnectionState().state, is(ConnectionState.disconnected)); - assertThat(connectionManager.getHost(), is(equalTo(opts.realtimeHost))); + assertThat(connectionManager.getHost(), is(equalTo(opts.endpoint))); connectionManager.close(); } @@ -163,8 +161,7 @@ protected boolean checkConnectivity() { /** *

* Verifies that fallback behaviour is applied and HTTP client is using same fallback - * endpoint, when the default realtime.ably.io endpoint is being used and has not been - * overriden, and a fallback is applied + * endpoint, when a routing policy endpoint is being used, and a fallback is applied *

*

* Spec: RTN17b, RTN17c @@ -177,12 +174,10 @@ public void connectionmanager_default_fallback_applied() throws AblyException { DebugOptions opts = new DebugOptions(testVars.keys[0].keyStr); fillInOptions(opts); - final Hosts hosts = new Hosts(null, Defaults.HOST_REALTIME, opts); + /* the routing policy endpoint has default fallback hosts */ + final Hosts hosts = new Hosts(opts); final String primaryHost = hosts.getPrimaryHost(); - /* clear the environment override, so we trigger default fallback behaviour */ - opts.environment = null; - /* set up mock transport */ MockWebsocketFactory mockTransport = new MockWebsocketFactory(); opts.transportFactory = mockTransport; @@ -218,7 +213,7 @@ public boolean matches(String hostname) { } /** - * Verify that when environment is overridden, no fallback is used by default + * Verify that when the endpoint is a hostname, no fallback is used by default * *

* Spec: RTN17b @@ -229,7 +224,9 @@ public void connectionmanager_default_endpoint_no_fallback() throws AblyExceptio DebugOptions opts = new DebugOptions(testVars.keys[0].keyStr); fillInOptions(opts); - final Hosts hosts = new Hosts(null, Defaults.HOST_REALTIME, opts); + /* a hostname endpoint has no default fallback hosts (REC2c2) */ + opts.endpoint = Defaults.getPrimaryDomain(opts.endpoint); + final Hosts hosts = new Hosts(opts); final String primaryHost = hosts.getPrimaryHost(); MockWebsocketFactory mockTransport = new MockWebsocketFactory(); @@ -268,7 +265,7 @@ public boolean matches(String hostname) { } /** - * Verify that when environment is overridden and fallback specified, the fallback is used + * Verify that when the endpoint is a hostname and fallback specified, the fallback is used * *

* Spec: RTN17b @@ -279,7 +276,9 @@ public void connectionmanager_default_endpoint_explicit_fallback() throws AblyEx DebugOptions opts = new DebugOptions(testVars.keys[0].keyStr); fillInOptions(opts); - final Hosts hosts = new Hosts(null, Defaults.HOST_REALTIME, opts); + /* a hostname endpoint has no default fallback hosts (REC2c2) */ + opts.endpoint = Defaults.getPrimaryDomain(opts.endpoint); + final Hosts hosts = new Hosts(opts); final String primaryHost = hosts.getPrimaryHost(); opts.fallbackHosts = new String[]{"fallback 1", "fallback 2"}; @@ -362,7 +361,7 @@ public void connectionmanager_close_while_connecting() throws AblyException { assertEquals(1 , connectionWaiter.getCount(ConnectionState.connecting)); assertEquals(0 , connectionWaiter.getCount(ConnectionState.connected)); assertEquals("Verify closed state is reached", ConnectionState.closed, ably.connection.state); - assertThat("fallback hasn't been invoked", connectionManager.getHost(), is(equalTo(opts.environment + "-realtime.ably.io"))); + assertThat("fallback hasn't been invoked", connectionManager.getHost(), is(equalTo(Defaults.getPrimaryDomain(opts.endpoint)))); } /** diff --git a/lib/src/test/java/io/ably/pubsub/test/realtime/RealtimeConnectFailTest.java b/lib/src/test/java/io/ably/pubsub/test/realtime/RealtimeConnectFailTest.java index 61d51483d..571110865 100644 --- a/lib/src/test/java/io/ably/pubsub/test/realtime/RealtimeConnectFailTest.java +++ b/lib/src/test/java/io/ably/pubsub/test/realtime/RealtimeConnectFailTest.java @@ -98,8 +98,7 @@ public void connect_fail_authorized_error() throws AblyException { @Test public void connect_fail_disconnected() throws AblyException { ClientOptions opts = createOptions(testVars.keys[0].keyStr); - opts.realtimeHost = "non.existent.host"; - opts.environment = null; + opts.endpoint = "non.existent.host"; PubSubRealtimeClient ably = RealtimeClientFactory.create(opts); ConnectionWaiter connectionWaiter = new ConnectionWaiter(ably.connection); @@ -118,8 +117,7 @@ public void connect_fail_disconnected() throws AblyException { public void connect_fail_suspended() { try { ClientOptions opts = createOptions(testVars.keys[0].keyStr); - opts.realtimeHost = "non.existent.host"; - opts.environment = null; + opts.endpoint = "non.existent.host"; PubSubRealtimeClient ably = RealtimeClientFactory.create(opts); ConnectionWaiter connectionWaiter = new ConnectionWaiter(ably.connection); @@ -170,8 +168,7 @@ public void connect_after_suspend_should_clean_msg_serial() throws AblyException public void connect_while_disconnected() { try { ClientOptions opts = createOptions(testVars.keys[0].keyStr); - opts.realtimeHost = "non.existent.host"; - opts.environment = null; + opts.endpoint = "non.existent.host"; PubSubRealtimeClient ably = RealtimeClientFactory.create(opts); ConnectionWaiter connectionWaiter = new ConnectionWaiter(ably.connection); @@ -564,8 +561,7 @@ public void disconnect_retry_connection_timeout_jitter() { PubSubRealtimeClient ably = null; try { ClientOptions opts = createOptions(testVars.keys[0].keyStr); - opts.realtimeHost = "non.existent.host"; - opts.environment = null; + opts.endpoint = "non.existent.host"; opts.disconnectedRetryTimeout = 5000; // Disconnected retry timeout set to 5 seconds. ably = RealtimeClientFactory.create(opts); diff --git a/lib/src/test/java/io/ably/pubsub/test/realtime/RealtimeHttpHeaderTest.java b/lib/src/test/java/io/ably/pubsub/test/realtime/RealtimeHttpHeaderTest.java index 5e9513817..079cea482 100644 --- a/lib/src/test/java/io/ably/pubsub/test/realtime/RealtimeHttpHeaderTest.java +++ b/lib/src/test/java/io/ably/pubsub/test/realtime/RealtimeHttpHeaderTest.java @@ -58,7 +58,7 @@ public void realtime_websocket_param_test() { String key = testVars.keys[0].keyStr; ClientOptions opts = new ClientOptions(key); opts.port = port; - opts.realtimeHost = "localhost"; + opts.endpoint = "localhost"; opts.tls = false; opts.useBinaryProtocol = testParams.useBinaryProtocol; @@ -98,7 +98,7 @@ public void realtime_websocket_param_test() { /* test echo option */ opts = new ClientOptions(key); opts.port = port; - opts.realtimeHost = "localhost"; + opts.endpoint = "localhost"; opts.tls = false; opts.useBinaryProtocol = testParams.useBinaryProtocol; opts.echoMessages = false; @@ -121,7 +121,7 @@ public void realtime_websocket_param_test() { String clientId = "test client id"; opts = new ClientOptions(); opts.port = port; - opts.realtimeHost = "localhost"; + opts.endpoint = "localhost"; opts.tls = false; opts.useBinaryProtocol = testParams.useBinaryProtocol; opts.useTokenAuth = true; diff --git a/lib/src/test/java/io/ably/pubsub/test/realtime/RealtimeInitTest.java b/lib/src/test/java/io/ably/pubsub/test/realtime/RealtimeInitTest.java index 343893b16..8a30d1682 100644 --- a/lib/src/test/java/io/ably/pubsub/test/realtime/RealtimeInitTest.java +++ b/lib/src/test/java/io/ably/pubsub/test/realtime/RealtimeInitTest.java @@ -76,7 +76,7 @@ public void init_host() { try { ClientOptions opts = new ClientOptions(testVars.keys[0].keyStr); String hostExpected = "some.other.host"; - opts.restHost = hostExpected; + opts.endpoint = hostExpected; ably = RealtimeClientFactory.create(opts); assertEquals("Unexpected host mismatch", hostExpected, ably.httpCore.getPrimaryHost()); } catch (AblyException e) { diff --git a/lib/src/test/java/io/ably/pubsub/test/realtime/RealtimeJWTTest.java b/lib/src/test/java/io/ably/pubsub/test/realtime/RealtimeJWTTest.java index 063bcc96a..efc2d65b5 100644 --- a/lib/src/test/java/io/ably/pubsub/test/realtime/RealtimeJWTTest.java +++ b/lib/src/test/java/io/ably/pubsub/test/realtime/RealtimeJWTTest.java @@ -222,7 +222,7 @@ public void auth_jwt_with_client_than_reauths_without_disconnecting() { /* create ably realtime with authUrl and params that include a ttl of 35 seconds */ DebugOptions options = new DebugOptions(testVars.keys[0].keyStr); - options.environment = createOptions().environment; + options.endpoint = createOptions().endpoint; options.authUrl = echoServer; options.authParams = mergeParams(keys, mediumTokenTtl); options.protocolListener = new RawProtocolListener() { @@ -325,7 +325,7 @@ public Object handleResponse(HttpCore.Response response, ErrorInfo error) throws /* create ably realtime with authCallback defined above */ DebugOptions options = new DebugOptions(testVars.keys[0].keyStr); - options.environment = createOptions().environment; + options.endpoint = createOptions().endpoint; options.authCallback = authCallback; options.protocolListener = new RawProtocolListener() { @Override diff --git a/lib/src/test/java/io/ably/pubsub/transport/DefaultsTest.java b/lib/src/test/java/io/ably/pubsub/transport/DefaultsTest.java index 5482a7ca0..b18150e2c 100644 --- a/lib/src/test/java/io/ably/pubsub/transport/DefaultsTest.java +++ b/lib/src/test/java/io/ably/pubsub/transport/DefaultsTest.java @@ -2,6 +2,7 @@ import org.junit.Test; +import static org.hamcrest.Matchers.emptyArray; import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertThat; @@ -12,14 +13,60 @@ public void protocol_version_CSV2() { assertThat(Defaults.ABLY_PROTOCOL_VERSION, is("6")); } + /** + * Spec: REC1a, REC2c1 + */ @Test - public void environmentFallbackHosts() { - assertThat(Defaults.getEnvironmentFallbackHosts("sandbox"), is(new 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" + public void default_endpoint() { + assertThat(Defaults.getPrimaryDomain(null), is("main.realtime.ably.net")); + assertThat(Defaults.getEndpointFallbackHosts(null), is(new String[] { + "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" })); + assertThat(Defaults.HOST_FALLBACKS, is(Defaults.getEndpointFallbackHosts(null))); + } + + /** + * Spec: REC1b4, REC2c4 + */ + @Test + public void routing_policy_endpoint() { + assertThat(Defaults.getPrimaryDomain("acme"), is("acme.realtime.ably.net")); + assertThat(Defaults.getEndpointFallbackHosts("acme"), is(new 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" + })); + } + + /** + * Spec: REC1b3, REC2c3 + */ + @Test + public void nonprod_routing_policy_endpoint() { + assertThat(Defaults.getPrimaryDomain("nonprod:sandbox"), is("sandbox.realtime.ably-nonprod.net")); + assertThat(Defaults.getEndpointFallbackHosts("nonprod:sandbox"), is(new 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" + })); + } + + /** + * Spec: REC1b2, REC2c2 + */ + @Test + public void hostname_endpoint() { + for (String hostname : new String[] { "foo.example.com", "localhost", "127.0.0.1", "::1" }) { + assertThat(Defaults.getPrimaryDomain(hostname), is(hostname)); + assertThat(Defaults.getEndpointFallbackHosts(hostname), is(emptyArray())); + } } } diff --git a/lib/src/test/java/io/ably/pubsub/transport/HostsTest.java b/lib/src/test/java/io/ably/pubsub/transport/HostsTest.java index 85bbdc6b1..c47dc1d2b 100644 --- a/lib/src/test/java/io/ably/pubsub/transport/HostsTest.java +++ b/lib/src/test/java/io/ably/pubsub/transport/HostsTest.java @@ -7,7 +7,6 @@ import static org.hamcrest.Matchers.nullValue; import static org.junit.Assert.assertThat; -import io.ably.pubsub.types.AblyException; import io.ably.pubsub.types.ClientOptions; import java.util.ArrayList; import java.util.List; @@ -18,47 +17,35 @@ public class HostsTest { private final ClientOptions options = new ClientOptions(); /** - * Tests for Hosts class (fallback hosts). + * Expect the default primary host and shuffled default fallback hosts when no endpoint is set. */ @Test - public void hosts_fallback() throws AblyException { + public void hosts_fallback_no_endpoint() { // When - Hosts hosts = new Hosts(Defaults.HOST_REALTIME, Defaults.HOST_REALTIME, options); + Hosts hosts = new Hosts(options); // Then List fallbackHosts = collectFallbackHosts(hosts); - assertThat(hosts.getPrimaryHost(), is(Defaults.HOST_REALTIME)); + assertThat(hosts.getPrimaryHost(), is("main.realtime.ably.net")); // the returned fallback hosts should have the same elements as default host fallbacks assertThat(fallbackHosts, containsInAnyOrder(Defaults.HOST_FALLBACKS)); // expect the fallback hosts to be shuffled assertThat(fallbackHosts, not(contains(Defaults.HOST_FALLBACKS))); } - /** - * Expect an exception when setting both realtimeHost and environment. - */ - @Test(expected = AblyException.class) - public void hosts_host_and_environment() throws AblyException { - // Given - options.environment = "myenv"; - - // When - new Hosts("overridden.ably.io", Defaults.HOST_REALTIME, options); - } - /** * Expect a null, when we provide empty array of fallback hosts */ @Test - public void hosts_fallback_empty_array() throws AblyException { + public void hosts_fallback_empty_array() { // Given options.fallbackHosts = new String[] {}; // When - Hosts hosts = new Hosts(Defaults.HOST_REALTIME, Defaults.HOST_REALTIME, options); + Hosts hosts = new Hosts(options); // Then - assertThat(hosts.getFallback(Defaults.HOST_REALTIME), nullValue()); + assertThat(hosts.getFallback(hosts.getPrimaryHost()), nullValue()); } /** @@ -66,17 +53,17 @@ public void hosts_fallback_empty_array() throws AblyException { * but in shuffled order and the right number of them. */ @Test - public void hosts_fallback_custom_hosts() throws AblyException { + public void hosts_fallback_custom_hosts() { // Given String[] customHosts = { "F.ably-realtime.com", "G.ably-realtime.com", "H.ably-realtime.com", "I.ably-realtime.com", "J.ably-realtime.com", "K.ably-realtime.com" }; options.fallbackHosts = customHosts; // When - Hosts hosts = new Hosts(Defaults.HOST_REALTIME, Defaults.HOST_REALTIME, options); + Hosts hosts = new Hosts(options); // Then List fallbackHosts = collectFallbackHosts(hosts); - assertThat(hosts.getPrimaryHost(), is(Defaults.HOST_REALTIME)); + assertThat(hosts.getPrimaryHost(), is("main.realtime.ably.net")); // the returned fallback hosts should have the same elements as custom host fallbacks assertThat(fallbackHosts, containsInAnyOrder(customHosts)); // expect the fallback hosts to be shuffled @@ -84,114 +71,100 @@ public void hosts_fallback_custom_hosts() throws AblyException { } /** - * Expect that returned host is contained within default host list - */ - @Test - public void hosts_fallback_no_custom_hosts() throws AblyException { - // When - Hosts hosts = new Hosts(null, Defaults.HOST_REALTIME, options); - - // Then - List fallbackHosts = collectFallbackHosts(hosts); - assertThat(hosts.getPrimaryHost(), is(Defaults.HOST_REALTIME)); - // the returned fallback hosts should have the same elements as default host fallbacks - assertThat(fallbackHosts, containsInAnyOrder(Defaults.HOST_FALLBACKS)); - // expect the fallback hosts to be shuffled - assertThat(fallbackHosts, not(contains(Defaults.HOST_FALLBACKS))); - } - - /** - * Expect a null, when realtimeHost is non-default + * Expect the routing policy's primary host and fallback hosts. + *

+ * Spec: REC1b4, REC2c4 */ @Test - public void hosts_fallback_overridden_host() throws AblyException { + public void hosts_routing_policy_endpoint() { // Given - String host = "overridden.ably.io"; - - // When - Hosts hosts = new Hosts(host, Defaults.HOST_REALTIME, options); - - // Then - assertThat(hosts.getFallback(host), nullValue()); - } - - /** - * Expect that returned fallback hosts containing the environment information. - */ - @Test - public void hosts_fallback_use_environment() throws AblyException { - // Given - options.environment = "sandbox"; - String[] expectedEnvironmentFallbackHosts = Defaults.getEnvironmentFallbackHosts(options.environment); + options.endpoint = "acme"; // When - Hosts hosts = new Hosts(null, Defaults.HOST_REALTIME, options); + Hosts hosts = new Hosts(options); // Then - assertThat(hosts.getPrimaryHost(), is("sandbox-" + Defaults.HOST_REALTIME)); - assertThat(collectFallbackHosts(hosts), containsInAnyOrder(expectedEnvironmentFallbackHosts)); + assertThat(hosts.getPrimaryHost(), is("acme.realtime.ably.net")); + assertThat(collectFallbackHosts(hosts), containsInAnyOrder(Defaults.getEndpointFallbackHosts("acme"))); } /** - * Expect no fallback hosts if the custom port is specified. + * Expect the nonprod routing policy's primary host and fallback hosts. + *

+ * Spec: REC1b3, REC2c3 */ @Test - public void hosts_no_fallback_when_port_is_defined() throws AblyException { + public void hosts_nonprod_endpoint() { // Given - options.port = 8080; + options.endpoint = "nonprod:sandbox"; // When - Hosts hosts = new Hosts(null, Defaults.HOST_REALTIME, options); + Hosts hosts = new Hosts(options); // Then - assertThat(hosts.getFallback(Defaults.HOST_REALTIME), nullValue()); + assertThat(hosts.getPrimaryHost(), is("sandbox.realtime.ably-nonprod.net")); + assertThat(collectFallbackHosts(hosts), containsInAnyOrder( + "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" + )); } /** - * Expect no fallback hosts if the custom TLS port is specified. + * Expect a null fallback when the endpoint is a hostname. + *

+ * Spec: REC1b2, REC2c2 */ @Test - public void hosts_no_fallback_when_tlsport_is_defined() throws AblyException { + public void hosts_no_fallback_for_hostname_endpoint() { // Given - options.tlsPort = 8081; + String host = "overridden.ably.io"; + options.endpoint = host; // When - Hosts hosts = new Hosts(null, Defaults.HOST_REALTIME, options); + Hosts hosts = new Hosts(options); // Then - assertThat(hosts.getFallback(Defaults.HOST_REALTIME), nullValue()); + assertThat(hosts.getPrimaryHost(), is(host)); + assertThat(hosts.getFallback(host), nullValue()); } /** - * It should return fallback hosts when custom fallbacks and port are provided. + * Expect custom fallback hosts to be used even when the endpoint is a hostname. + *

+ * Spec: REC2a2 */ @Test - public void hosts_fallback_when_fallback_hosts_and_port_are_defined() throws AblyException { + public void hosts_fallback_for_hostname_endpoint_and_fallback_hosts() { // Given - options.port = 8081; + options.endpoint = "custom.ably.com"; options.fallbackHosts = new String[] { "custom-fallback.ably.com" }; // When - Hosts hosts = new Hosts(null, Defaults.HOST_REALTIME, options); + Hosts hosts = new Hosts(options); // Then - assertThat(hosts.getFallback(Defaults.HOST_REALTIME), is("custom-fallback.ably.com")); + assertThat(hosts.getFallback("custom.ably.com"), is("custom-fallback.ably.com")); } /** - * It should return fallback hosts when host, custom fallbacks, and port are provided. + * Expect default fallback hosts even when a custom port is specified. + *

+ * Spec: REC2c1 */ @Test - public void hosts_fallback_when_host_and_fallback_hosts_and_port_are_defined() throws AblyException { + public void hosts_fallback_when_port_is_defined() { // Given + options.port = 8080; options.tlsPort = 8081; - options.fallbackHosts = new String[] { "custom-fallback.ably.com" }; // When - Hosts hosts = new Hosts("custom.ably.com", Defaults.HOST_REALTIME, options); + Hosts hosts = new Hosts(options); // Then - assertThat(hosts.getFallback("custom.ably.com"), is("custom-fallback.ably.com")); + assertThat(collectFallbackHosts(hosts), containsInAnyOrder(Defaults.HOST_FALLBACKS)); } private List collectFallbackHosts(Hosts hosts) { diff --git a/lib/src/test/kotlin/io/ably/pubsub/uts/integration/standard/realtime/ChannelHistoryTest.kt b/lib/src/test/kotlin/io/ably/pubsub/uts/integration/standard/realtime/ChannelHistoryTest.kt index 790dc9df5..7e636d546 100644 --- a/lib/src/test/kotlin/io/ably/pubsub/uts/integration/standard/realtime/ChannelHistoryTest.kt +++ b/lib/src/test/kotlin/io/ably/pubsub/uts/integration/standard/realtime/ChannelHistoryTest.kt @@ -118,8 +118,7 @@ class ChannelHistoryTest { /** A realtime client wired straight to the nonprod sandbox (no proxy). */ private fun newClient(useBinaryProtocol: Boolean): PubSubRealtimeClient = TestRealtimeClient { key = app.defaultKey - realtimeHost = SandboxApp.sandboxHost - restHost = SandboxApp.sandboxHost + endpoint = SandboxApp.sandboxEndpoint this.useBinaryProtocol = useBinaryProtocol autoConnect = false } diff --git a/lib/src/test/kotlin/io/ably/pubsub/uts/integration/standard/realtime/TokenRequestTest.kt b/lib/src/test/kotlin/io/ably/pubsub/uts/integration/standard/realtime/TokenRequestTest.kt index 900951928..50b7a1c83 100644 --- a/lib/src/test/kotlin/io/ably/pubsub/uts/integration/standard/realtime/TokenRequestTest.kt +++ b/lib/src/test/kotlin/io/ably/pubsub/uts/integration/standard/realtime/TokenRequestTest.kt @@ -69,14 +69,13 @@ class TokenRequestTest { // Client A signs TokenRequests locally with the API key (no network). val creator = TestHttpClient { key = app.defaultKey - restHost = SandboxApp.sandboxHost + endpoint = SandboxApp.sandboxEndpoint } // Client B connects using a TokenRequest produced by client A. val client = tokenConsumingClient { authCallback = Auth.TokenCallback { params -> creator.auth.createTokenRequest(params, null) } - realtimeHost = SandboxApp.sandboxHost - restHost = SandboxApp.sandboxHost + endpoint = SandboxApp.sandboxEndpoint useBinaryProtocol = false autoConnect = false } @@ -103,7 +102,7 @@ class TokenRequestTest { val creator = TestHttpClient { key = app.defaultKey - restHost = SandboxApp.sandboxHost + endpoint = SandboxApp.sandboxEndpoint } // The TokenRequest is signed with the specific clientId, producing a token that @@ -114,8 +113,7 @@ class TokenRequestTest { creator.auth.createTokenRequest(params, null) } clientId = testClientId - realtimeHost = SandboxApp.sandboxHost - restHost = SandboxApp.sandboxHost + endpoint = SandboxApp.sandboxEndpoint useBinaryProtocol = false autoConnect = false } diff --git a/liveobjects/src/test/kotlin/io/ably/pubsub/liveobjects/integration/setup/Sandbox.kt b/liveobjects/src/test/kotlin/io/ably/pubsub/liveobjects/integration/setup/Sandbox.kt index a90a06803..edbddc48d 100644 --- a/liveobjects/src/test/kotlin/io/ably/pubsub/liveobjects/integration/setup/Sandbox.kt +++ b/liveobjects/src/test/kotlin/io/ably/pubsub/liveobjects/integration/setup/Sandbox.kt @@ -33,7 +33,7 @@ internal fun Sandbox.createRealtimeClient(options: ClientOptions.() -> Unit): Pu val clientOptions = ClientOptions().apply { apply(options) key = apiKey - environment = "sandbox" + endpoint = SandboxApp.sandboxEndpoint } return RealtimeClientFactory.create(clientOptions) } @@ -41,7 +41,7 @@ internal fun Sandbox.createRealtimeClient(options: ClientOptions.() -> Unit): Pu internal fun Sandbox.createHttpObjects(): HttpObjects { val options = ClientOptions().apply { key = apiKey - environment = "sandbox" + endpoint = SandboxApp.sandboxEndpoint useBinaryProtocol = false } return HttpObjects(options) diff --git a/liveobjects/src/test/kotlin/io/ably/pubsub/liveobjects/uts/integration/Helpers.kt b/liveobjects/src/test/kotlin/io/ably/pubsub/liveobjects/uts/integration/Helpers.kt index a40022587..dcc61d3c7 100644 --- a/liveobjects/src/test/kotlin/io/ably/pubsub/liveobjects/uts/integration/Helpers.kt +++ b/liveobjects/src/test/kotlin/io/ably/pubsub/liveobjects/uts/integration/Helpers.kt @@ -123,11 +123,9 @@ fun provisionObjectsViaHttp(apiKey: String, channelName: String, operations: Lis val rest = HttpClientFactory.create( ClientOptions().apply { key = apiKey - // Target the same nonprod sandbox host that SandboxApp and the realtime clients use - // (sandbox.realtime.ably-nonprod.net) — NOT environment="sandbox", which resolves to the - // legacy prod-sandbox host sandbox-rest.ably.io (Hosts.java also forbids setting both - // environment and restHost). Matches standard_test_pool.md (ably/specification#497). - restHost = SandboxApp.sandboxHost + // Target the same nonprod sandbox endpoint that SandboxApp and the realtime clients use. + // Matches standard_test_pool.md (ably/specification#497). + endpoint = SandboxApp.sandboxEndpoint useBinaryProtocol = false }, ) diff --git a/liveobjects/src/test/kotlin/io/ably/pubsub/liveobjects/uts/integration/ObjectsLifecycleTest.kt b/liveobjects/src/test/kotlin/io/ably/pubsub/liveobjects/uts/integration/ObjectsLifecycleTest.kt index d3f43f529..10faaab84 100644 --- a/liveobjects/src/test/kotlin/io/ably/pubsub/liveobjects/uts/integration/ObjectsLifecycleTest.kt +++ b/liveobjects/src/test/kotlin/io/ably/pubsub/liveobjects/uts/integration/ObjectsLifecycleTest.kt @@ -261,8 +261,7 @@ class ObjectsLifecycleTest { /** A realtime client wired straight to the nonprod sandbox (no proxy). */ private fun newClient(useBinaryProtocol: Boolean): PubSubRealtimeClient = TestRealtimeClient { key = app.defaultKey - realtimeHost = SandboxApp.sandboxHost - restHost = SandboxApp.sandboxHost + endpoint = SandboxApp.sandboxEndpoint this.useBinaryProtocol = useBinaryProtocol autoConnect = false } diff --git a/liveobjects/src/test/kotlin/io/ably/pubsub/liveobjects/uts/integration/ObjectsSyncTest.kt b/liveobjects/src/test/kotlin/io/ably/pubsub/liveobjects/uts/integration/ObjectsSyncTest.kt index 9d1bcf3a1..a3e8bb923 100644 --- a/liveobjects/src/test/kotlin/io/ably/pubsub/liveobjects/uts/integration/ObjectsSyncTest.kt +++ b/liveobjects/src/test/kotlin/io/ably/pubsub/liveobjects/uts/integration/ObjectsSyncTest.kt @@ -170,8 +170,7 @@ class ObjectsSyncTest { /** A realtime client wired straight to the nonprod sandbox (no proxy). */ private fun newClient(useBinaryProtocol: Boolean): PubSubRealtimeClient = TestRealtimeClient { key = app.defaultKey - realtimeHost = SandboxApp.sandboxHost - restHost = SandboxApp.sandboxHost + endpoint = SandboxApp.sandboxEndpoint this.useBinaryProtocol = useBinaryProtocol autoConnect = false } diff --git a/liveobjects/src/test/kotlin/io/ably/pubsub/liveobjects/uts/proxy/ObjectsFaultsTest.kt b/liveobjects/src/test/kotlin/io/ably/pubsub/liveobjects/uts/proxy/ObjectsFaultsTest.kt index 2a8705fe2..049a19417 100644 --- a/liveobjects/src/test/kotlin/io/ably/pubsub/liveobjects/uts/proxy/ObjectsFaultsTest.kt +++ b/liveobjects/src/test/kotlin/io/ably/pubsub/liveobjects/uts/proxy/ObjectsFaultsTest.kt @@ -371,8 +371,7 @@ class ObjectsFaultsTest { /** A realtime client connected straight to the nonprod sandbox (no proxy). */ private fun directClient(): PubSubRealtimeClient = TestRealtimeClient { key = app.defaultKey - realtimeHost = SandboxApp.sandboxHost - restHost = SandboxApp.sandboxHost + endpoint = SandboxApp.sandboxEndpoint autoConnect = false } diff --git a/pubsub-adapter/src/test/kotlin/com/ably/Utils.kt b/pubsub-adapter/src/test/kotlin/com/ably/Utils.kt index 73546594d..9e37ee9a2 100644 --- a/pubsub-adapter/src/test/kotlin/com/ably/Utils.kt +++ b/pubsub-adapter/src/test/kotlin/com/ably/Utils.kt @@ -23,8 +23,7 @@ fun createCoreRealtimeClient(port: Int): PubSubRealtimeClient { val options = ClientOptions("xxxxx:yyyyyyy").apply { this.port = port useBinaryProtocol = false - realtimeHost = "localhost" - restHost = "localhost" + endpoint = "localhost" tls = false autoConnect = false } diff --git a/server/src/main/java/io/ably/pubsub/server/PubSubServer.java b/server/src/main/java/io/ably/pubsub/server/PubSubServer.java index 6391962dc..0cfd0674c 100644 --- a/server/src/main/java/io/ably/pubsub/server/PubSubServer.java +++ b/server/src/main/java/io/ably/pubsub/server/PubSubServer.java @@ -309,13 +309,13 @@ public T headers(Map headers) { } /** - * Sets {@link ClientOptions#restHost}. + * Sets {@link ClientOptions#endpoint}. * - * @param restHost the value to set. + * @param endpoint the value to set. * @return this builder. */ - public T restHost(String restHost) { - options.restHost = restHost; + public T endpoint(String endpoint) { + options.endpoint = endpoint; return self(); } @@ -363,17 +363,6 @@ public T proxy(ProxyOptions proxy) { return self(); } - /** - * Sets {@link ClientOptions#environment}. - * - * @param environment the value to set. - * @return this builder. - */ - public T environment(String environment) { - options.environment = environment; - return self(); - } - /** * Sets {@link ClientOptions#idempotentRestPublishing}. * @@ -557,17 +546,6 @@ public static final class RealtimeClientBuilder extends ClientBuilder/`) use it alone — -connecting straight to `SandboxApp.sandboxHost` with no proxy and no fault rules, for happy-path +connecting straight to `SandboxApp.sandboxEndpoint` with no proxy and no fault rules, for happy-path interop. --- @@ -747,7 +745,7 @@ alone reaching quiescence. > (`lib/src/test/kotlin/io/ably/pubsub/uts/integration/standard/realtime/`, e.g. `ChannelHistoryTest`) > and `:liveobjects` — see §13. -It talks to the real backend but connects *straight* to `SandboxApp.sandboxHost` — no `ProxyManager`, +It talks to the real backend but connects *straight* to `SandboxApp.sandboxEndpoint` — no `ProxyManager`, no `ProxySession`, no `connectThroughProxy`. It's the shape every happy-path interop spec (connect/publish/subscribe/history) follows. @@ -760,13 +758,12 @@ no `ProxySession`, no `connectThroughProxy`. It's the shape every happy-path int ``` ### 10.2 The clients — wired straight to the sandbox -Two tiny helpers point the **real** transports at the sandbox host (no proxy in between). Setting -explicit hosts auto-disables fallback hosts (REC2c2), so there's nothing else to configure: +Two tiny helpers point the **real** transports at the sandbox endpoint (no proxy in between), so +there's nothing else to configure: ```kotlin private fun newRealtimeClient(useBinaryProtocol: Boolean): PubSubRealtimeClient = TestRealtimeClient { key = app.defaultKey - realtimeHost = SandboxApp.sandboxHost // sandbox.realtime.ably-nonprod.net - restHost = SandboxApp.sandboxHost + endpoint = SandboxApp.sandboxEndpoint // nonprod:sandbox this.useBinaryProtocol = useBinaryProtocol autoConnect = false } @@ -812,7 +809,7 @@ appear in the REST `history()`. The integration-specific techniques on show: - **Order assertion.** History defaults to newest-first, so `items[0]` is `event3` … `items[2]` is `event1`. **What this test teaches about the infra:** `SandboxApp`-only provisioning, the direct-sandbox client -wiring (`realtimeHost`/`restHost` from `SandboxApp.sandboxHost`, no proxy), the protocol-variant +wiring (`endpoint` from `SandboxApp.sandboxEndpoint`, no proxy), the protocol-variant `@ParameterizedTest`, awaiting a publish ack via `Callback`, and `pollUntil` over a real REST `history()` call. @@ -1186,7 +1183,7 @@ implicit. |------|--------------------|------| | `proxy/ProxyManager.kt` | `object ProxyManager`: `ensureProxy(timeoutMs)`, `stopProxy()`, `CONTROL_PORT=10100`; pinned `PROXY_VERSION=v0.3.0` + per-arch checksums; `uts.proxy.localPath` override | Downloads/verifies/launches the `uts-proxy` binary; one shared process per run. *(package `…integration.proxy`)* | | `proxy/ProxySession.kt` | `class ProxySession` (`create(rules,port,timeoutMs,realtimeHost,restHost)`, `addRules`, `triggerAction`, `getLog(): List`, `close`, `sessionId`, `proxyPort`, `proxyHost`); `data class Event`; `typealias ProxyRule`; rule builders `wsConnectRule`/`wsFrameToClientRule`/`wsFrameToServerRule`/`httpRequestRule`; `ClientOptionsBuilder.connectThroughProxy(session)` | Typed client for the proxy control REST API + client wiring. *(package `…integration.proxy`)* | -| `SandboxApp.kt` | `class SandboxApp` (`create()`, `delete()`, `appId`, `defaultKey`, `keys`); `SandboxApp.sandboxHost` (`sandbox.realtime.ably-nonprod.net`) | Provisions/tears down a throwaway sandbox app from `ably-common`'s `test-app-setup.json`; owns the single upstream sandbox host constant. *(package `…integration`)* | +| `SandboxApp.kt` | `class SandboxApp` (`create()`, `delete()`, `appId`, `defaultKey`, `keys`); `SandboxApp.sandboxEndpoint` (`nonprod:sandbox`), `SandboxApp.sandboxHost` (`sandbox.realtime.ably-nonprod.net`) | Provisions/tears down a throwaway sandbox app from `ably-common`'s `test-app-setup.json`; owns the sandbox endpoint and upstream host constants. *(package `…integration`)* | ### B.3 Shared helpers & tests diff --git a/uts/src/main/kotlin/io/ably/pubsub/uts/infra/integration/SandboxApp.kt b/uts/src/main/kotlin/io/ably/pubsub/uts/infra/integration/SandboxApp.kt index 605f86a12..0752f663e 100644 --- a/uts/src/main/kotlin/io/ably/pubsub/uts/infra/integration/SandboxApp.kt +++ b/uts/src/main/kotlin/io/ably/pubsub/uts/infra/integration/SandboxApp.kt @@ -68,10 +68,13 @@ class SandboxApp private constructor( companion object { /** - * The Ably **nonprod sandbox** host — the `nonprod:sandbox` endpoint (used uniformly across the - * realtime/objects/rest integration specs), resolved to a hostname. Realtime and REST share this - * single host, so point both transports at it: set `realtimeHost` and/or `restHost` from here. + * The Ably **nonprod sandbox** endpoint, used uniformly across the realtime/objects/rest + * integration specs. Set it as the client's `endpoint`; realtime and REST share the host it + * resolves to ([sandboxHost]). */ + const val sandboxEndpoint = "nonprod:sandbox" + + /** The hostname [sandboxEndpoint] resolves to, for direct HTTP calls. */ const val sandboxHost = "sandbox.realtime.ably-nonprod.net" private const val sandboxBaseUrl = "https://$sandboxHost" diff --git a/uts/src/main/kotlin/io/ably/pubsub/uts/infra/integration/proxy/ProxySession.kt b/uts/src/main/kotlin/io/ably/pubsub/uts/infra/integration/proxy/ProxySession.kt index ca8963b40..7928590a9 100644 --- a/uts/src/main/kotlin/io/ably/pubsub/uts/infra/integration/proxy/ProxySession.kt +++ b/uts/src/main/kotlin/io/ably/pubsub/uts/infra/integration/proxy/ProxySession.kt @@ -3,6 +3,7 @@ package io.ably.pubsub.uts.infra.integration.proxy import com.google.gson.Gson import com.google.gson.JsonObject import com.google.gson.reflect.TypeToken +import io.ably.pubsub.transport.Defaults import io.ably.pubsub.uts.infra.integration.SandboxApp import io.ably.pubsub.uts.infra.unit.ClientOptionsBuilder import io.ktor.client.HttpClient @@ -214,20 +215,20 @@ class ProxySession private constructor( * @param rules Initial rule set applied to all traffic through this session. * @param port Specific port to listen on; `0` (default) lets the proxy choose. * @param timeoutMs Session idle-timeout in ms; `null` uses the proxy default (30 000 ms). - * @param realtimeHost Upstream Ably realtime host (defaults to sandbox). - * @param restHost Upstream Ably REST host (defaults to sandbox). + * @param endpoint Upstream Ably endpoint (defaults to sandbox). Realtime and REST share the + * host it resolves to (REC1), so the proxy targets that host for both. */ suspend fun create( rules: List = emptyList(), port: Int = 0, timeoutMs: Long? = null, - realtimeHost: String = SandboxApp.sandboxHost, - restHost: String = SandboxApp.sandboxHost, + endpoint: String = SandboxApp.sandboxEndpoint, ): ProxySession { + val upstreamHost = Defaults.getPrimaryDomain(endpoint) val body = JsonObject().apply { add("target", JsonObject().apply { - addProperty("realtimeHost", realtimeHost) - addProperty("restHost", restHost) + addProperty("realtimeHost", upstreamHost) + addProperty("restHost", upstreamHost) }) add("rules", gson.toJsonTree(rules)) if (port != 0) addProperty("port", port) @@ -346,15 +347,14 @@ class ProxySession private constructor( * } * ``` * - * Sets `realtimeHost` and `restHost` to the proxy host, `port` to the session's assigned port, + * Sets `endpoint` to the proxy host, `port` to the session's assigned port, * and `tls = false` (the proxy serves plain HTTP/WS; TLS is only used upstream to the sandbox). * `useBinaryProtocol` is already `false` by default in [ClientOptionsBuilder]. * - * Setting explicit hosts disables fallback hosts automatically, so no `fallbackHosts` is needed. + * A hostname endpoint has no default fallback hosts (REC2c2), so no `fallbackHosts` is needed. */ fun ClientOptionsBuilder.connectThroughProxy(session: ProxySession) { - realtimeHost = session.proxyHost - restHost = session.proxyHost + endpoint = session.proxyHost port = session.proxyPort tls = false } diff --git a/uts/src/test/kotlin/io/ably/pubsub/uts/integration/standard/IntegrationInfraSmokeTest.kt b/uts/src/test/kotlin/io/ably/pubsub/uts/integration/standard/IntegrationInfraSmokeTest.kt index 73b44b18c..95c2d03ce 100644 --- a/uts/src/test/kotlin/io/ably/pubsub/uts/integration/standard/IntegrationInfraSmokeTest.kt +++ b/uts/src/test/kotlin/io/ably/pubsub/uts/integration/standard/IntegrationInfraSmokeTest.kt @@ -128,8 +128,7 @@ class IntegrationInfraSmokeTest { /** A realtime client wired straight to the nonprod sandbox (no proxy). */ private fun newRealtimeClient(useBinaryProtocol: Boolean): PubSubRealtimeClient = TestRealtimeClient { key = app.defaultKey - realtimeHost = SandboxApp.sandboxHost - restHost = SandboxApp.sandboxHost + endpoint = SandboxApp.sandboxEndpoint this.useBinaryProtocol = useBinaryProtocol autoConnect = false } @@ -137,7 +136,7 @@ class IntegrationInfraSmokeTest { /** A REST client wired straight to the nonprod sandbox (the REST half of the infra). */ private fun newHttpClient(useBinaryProtocol: Boolean): PubSubHttpClient = TestHttpClient { key = app.defaultKey - restHost = SandboxApp.sandboxHost + endpoint = SandboxApp.sandboxEndpoint this.useBinaryProtocol = useBinaryProtocol }