From f0f58a959aae7fba582532847ab8cf132b543db4 Mon Sep 17 00:00:00 2001 From: evgeny Date: Thu, 8 Oct 2026 19:52:43 +0100 Subject: [PATCH] Replace environment, restHost and realtimeHost with endpoint 2.0 adopts the REC1/REC2 host model (ADR-119): a single ClientOptions.endpoint selects where both REST requests and the realtime connection go, and the legacy environment, restHost and realtimeHost options are removed outright, so old code fails to compile rather than silently connecting to production. - endpoint resolves as: unset -> main.realtime.ably.net; a routing policy name -> [name].realtime.ably.net; nonprod:[name] -> [name].realtime.ably-nonprod.net; a hostname (contains '.' or '::', or is localhost) -> used as given. Default fallbacks follow the endpoint ([name].[a-e].fallback.ably-realtime[-nonprod].com) and a hostname has none. - Hosts now resolves from ClientOptions alone, so HttpCore and ConnectionManager share one primary domain. The restHost/environment conflict check, the rest.ably.io/realtime.ably.io defaults and the -[a-e]-fallback hosts go with the legacy options. - An explicit fallbackHosts always replaces the defaults (REC2a2), and a custom port or tlsPort no longer suppresses the default fallbacks, as the spec has no such rule. - The server and device builders swap restHost(), realtimeHost() and environment() for endpoint(). The tests move to endpoint throughout: ABLY_ENV, ABLY_REST_HOST and ABLY_REALTIME_HOST become ABLY_ENDPOINT (default nonprod:sandbox), UTS and LiveObjects sandbox clients use the new SandboxApp.sandboxEndpoint, and mock/proxy clients use a localhost endpoint. Tests of the legacy host derivation and option conflicts are replaced by DefaultsTest/HostsTest cases for each endpoint form, custom fallbackHosts, and ports no longer disabling fallbacks. UPGRADING.md gains the migration table and the firewall allowlist note. Co-Authored-By: Claude Opus 5.5 (1M context) --- .claude/skills/uts-to-kotlin/SKILL.md | 11 +- .../references/objects-mapping.md | 7 +- CONTRIBUTING.md | 6 +- UPGRADING.md | 50 +++++++ .../pubsub/test/android/AndroidSuite.java | 2 +- .../ably/pubsub/push/ActivationContext.java | 2 +- .../io/ably/pubsub/device/PubSubDevice.kt | 22 +-- .../kotlin/com/ably/example/MainActivity.kt | 4 +- .../com/ably/example/screen/MainScreen.kt | 2 +- .../io/ably/pubsub/debug/DebugOptions.java | 4 +- .../java/io/ably/pubsub/http/HttpCore.java | 2 +- .../java/io/ably/pubsub/http/HttpUtils.java | 13 +- .../pubsub/transport/ConnectionManager.java | 2 +- .../io/ably/pubsub/transport/Defaults.java | 75 ++++++++-- .../java/io/ably/pubsub/transport/Hosts.java | 60 ++------ .../pubsub/transport/WebSocketTransport.java | 2 +- .../io/ably/pubsub/types/ClientOptions.java | 38 ++--- .../io/ably/pubsub/http/HttpUtilsTest.java | 32 ++++ .../io/ably/pubsub/test/common/Setup.java | 37 ++--- .../ably/pubsub/test/http/HttpAuthTest.java | 17 +-- .../ably/pubsub/test/http/HttpClientTest.java | 6 +- .../ably/pubsub/test/http/HttpErrorTest.java | 9 +- .../ably/pubsub/test/http/HttpHeaderTest.java | 6 +- .../ably/pubsub/test/http/HttpInitTest.java | 45 +++--- .../io/ably/pubsub/test/http/HttpJWTTest.java | 2 +- .../ably/pubsub/test/http/HttpProxyTest.java | 2 + .../pubsub/test/http/HttpRequestTest.java | 4 +- .../io/ably/pubsub/test/http/HttpTest.java | 38 ++--- .../ably/pubsub/test/http/HttpTimeTest.java | 5 +- .../test/realtime/ConnectionManagerTest.java | 35 +++-- .../realtime/RealtimeConnectFailTest.java | 12 +- .../test/realtime/RealtimeHttpHeaderTest.java | 6 +- .../test/realtime/RealtimeInitTest.java | 2 +- .../pubsub/test/realtime/RealtimeJWTTest.java | 4 +- .../ably/pubsub/transport/DefaultsTest.java | 61 +++++++- .../io/ably/pubsub/transport/HostsTest.java | 137 +++++++----------- .../standard/realtime/ChannelHistoryTest.kt | 3 +- .../standard/realtime/TokenRequestTest.kt | 10 +- .../liveobjects/integration/setup/Sandbox.kt | 4 +- .../liveobjects/uts/integration/Helpers.kt | 8 +- .../uts/integration/ObjectsLifecycleTest.kt | 3 +- .../uts/integration/ObjectsSyncTest.kt | 3 +- .../uts/proxy/ObjectsFaultsTest.kt | 3 +- .../src/test/kotlin/com/ably/Utils.kt | 3 +- .../io/ably/pubsub/server/PubSubServer.java | 30 +--- .../ably/pubsub/server/PubSubServerTest.java | 2 +- uts/README.md | 35 ++--- .../uts/infra/integration/SandboxApp.kt | 9 +- .../infra/integration/proxy/ProxySession.kt | 20 +-- .../standard/IntegrationInfraSmokeTest.kt | 5 +- 50 files changed, 468 insertions(+), 432 deletions(-) create mode 100644 lib/src/test/java/io/ably/pubsub/http/HttpUtilsTest.java 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 }