diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7bf2ca18c..ae9624b6f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -35,6 +35,34 @@ The Android-specific library AAR is built with: (The `ANDROID_HOME` environment variable must be set appropriately.) +## Adding a New Network Engine Implementation + +Currently, `ably-java` supports two different engines for network operations (HTTP calls and WebSocket connections): + +- **Default Engine**: Utilizes the built-in `HttpUrlConnection` for HTTP calls and the TooTallNate/Java-WebSocket library for WebSocket connections. +- **OkHttp Engine**: Utilizes the OkHttp library for both HTTP and WebSocket connections. + +These engines are designed to be swappable. By default, the library comes with the default engine, but you can easily replace it with the OkHttp engine: + +```kotlin +implementation("io.ably:ably-java:$ABLY_VERSION") { + exclude(group = "io.ably", module = "network-client-default") +} +runtimeOnly("io.ably:network-client-okhttp:$ABLY_VERSION") +``` + +### How to Add a New Network Engine + +To add a new network engine, follow these steps: + +1. **Implement the interfaces**: + - Implement the `HttpEngineFactory` and `WebSocketEngineFactory` interfaces for your custom engine. + +2. **Register the engine**: + - Modify the `getFirstAvailable()` methods in these interfaces to include your new implementation. + +Once done, your custom network engine will be available for use within `ably-java`. + ### Code Standard #### Checkstyle diff --git a/network-client-core/src/main/java/io/ably/lib/network/HttpCall.java b/network-client-core/src/main/java/io/ably/lib/network/HttpCall.java index 0d9226cbd..87e77aa40 100644 --- a/network-client-core/src/main/java/io/ably/lib/network/HttpCall.java +++ b/network-client-core/src/main/java/io/ably/lib/network/HttpCall.java @@ -1,6 +1,18 @@ package io.ably.lib.network; +/** + * Cancelable Http request call + *

+ * Implementation should be thread-safe + */ public interface HttpCall { + /** + * Synchronously execute Http request and return response from te server + */ HttpResponse execute(); + + /** + * Cancel pending Http request + */ void cancel(); } diff --git a/network-client-core/src/main/java/io/ably/lib/network/HttpEngine.java b/network-client-core/src/main/java/io/ably/lib/network/HttpEngine.java index 0b4fa29f3..eae17fd4a 100644 --- a/network-client-core/src/main/java/io/ably/lib/network/HttpEngine.java +++ b/network-client-core/src/main/java/io/ably/lib/network/HttpEngine.java @@ -1,6 +1,18 @@ package io.ably.lib.network; +/** + * An HTTP engine instance that can make cancelable HTTP requests. + * It contains some engine-wide configurations, such as proxy settings, + * if it operates under a corporate proxy. + */ public interface HttpEngine { + /** + * @return cancelable Http request call + */ HttpCall call(HttpRequest request); + + /** + * @return true if it uses proxy, false otherwise + */ boolean isUsingProxy(); } diff --git a/network-client-core/src/main/java/io/ably/lib/network/HttpEngineFactory.java b/network-client-core/src/main/java/io/ably/lib/network/HttpEngineFactory.java index e93812db9..e388064a0 100644 --- a/network-client-core/src/main/java/io/ably/lib/network/HttpEngineFactory.java +++ b/network-client-core/src/main/java/io/ably/lib/network/HttpEngineFactory.java @@ -2,11 +2,14 @@ import java.lang.reflect.InvocationTargetException; +/** + * The HttpEngineFactory is a utility class that produces a common HTTP Engine API + * for different implementations. Currently, it supports: + * - HttpURLConnection ({@link EngineType#DEFAULT}) + * - OkHttp ({@link EngineType#OKHTTP}) + */ public interface HttpEngineFactory { - HttpEngine create(HttpEngineConfig config); - EngineType getEngineType(); - static HttpEngineFactory getFirstAvailable() { HttpEngineFactory okHttpFactory = tryGetOkHttpFactory(); if (okHttpFactory != null) return okHttpFactory; @@ -19,7 +22,8 @@ static HttpEngineFactory tryGetOkHttpFactory() { try { Class okHttpFactoryClass = Class.forName("io.ably.lib.network.OkHttpEngineFactory"); return (HttpEngineFactory) okHttpFactoryClass.getDeclaredConstructor().newInstance(); - } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) { + } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | NoSuchMethodException | + InvocationTargetException e) { return null; } } @@ -28,8 +32,13 @@ static HttpEngineFactory tryGetDefaultFactory() { try { Class defaultFactoryClass = Class.forName("io.ably.lib.network.DefaultHttpEngineFactory"); return (HttpEngineFactory) defaultFactoryClass.getDeclaredConstructor().newInstance(); - } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) { + } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | NoSuchMethodException | + InvocationTargetException e) { return null; } } + + HttpEngine create(HttpEngineConfig config); + + EngineType getEngineType(); } diff --git a/network-client-core/src/main/java/io/ably/lib/network/WebSocketClient.java b/network-client-core/src/main/java/io/ably/lib/network/WebSocketClient.java index b3cd58108..9452fc132 100644 --- a/network-client-core/src/main/java/io/ably/lib/network/WebSocketClient.java +++ b/network-client-core/src/main/java/io/ably/lib/network/WebSocketClient.java @@ -1,7 +1,14 @@ package io.ably.lib.network; +/** + * WebSocketClient instance bind to the specified URI. + * The connection will be established once you call connect. + */ public interface WebSocketClient { + /** + * Establish connection to the Websocket server + */ void connect(); /** @@ -12,7 +19,7 @@ public interface WebSocketClient { /** * Sends the closing handshake. May be sent in response to any other handshake. * - * @param code the closing code + * @param code the closing code * @param reason the closing message */ void close(int code, String reason); @@ -21,13 +28,23 @@ public interface WebSocketClient { * This will close the connection immediately without a proper close handshake. The code and the * message therefore won't be transferred over the wire also they will be forwarded to `onClose`. * - * @param code the closing code + * @param code the closing code * @param reason the closing message **/ void cancel(int code, String reason); + /** + * Sends binary message to the connected webSocket server. + * + * @param message The byte-Array of data to send to the WebSocket server. + */ void send(byte[] message); + /** + * Sends message to the connected websocket server. + * + * @param message The string which will be transmitted. + */ void send(String message); } diff --git a/network-client-core/src/main/java/io/ably/lib/network/WebSocketEngine.java b/network-client-core/src/main/java/io/ably/lib/network/WebSocketEngine.java index 32bd92bdb..a4a236757 100644 --- a/network-client-core/src/main/java/io/ably/lib/network/WebSocketEngine.java +++ b/network-client-core/src/main/java/io/ably/lib/network/WebSocketEngine.java @@ -1,5 +1,8 @@ package io.ably.lib.network; +/** + * Create WebSocket client bind to the specific URL + */ public interface WebSocketEngine { WebSocketClient create(String url, WebSocketListener listener); } diff --git a/network-client-core/src/main/java/io/ably/lib/network/WebSocketEngineFactory.java b/network-client-core/src/main/java/io/ably/lib/network/WebSocketEngineFactory.java index be0247cb5..ce22567b3 100644 --- a/network-client-core/src/main/java/io/ably/lib/network/WebSocketEngineFactory.java +++ b/network-client-core/src/main/java/io/ably/lib/network/WebSocketEngineFactory.java @@ -2,10 +2,13 @@ import java.lang.reflect.InvocationTargetException; +/** + * The WebSocketEngineFactory is a utility class that produces a common WebSocket Engine API + * for different implementations. Currently, it supports: + * - TooTallNate/Java-WebSocket ({@link EngineType#DEFAULT}) + * - OkHttp ({@link EngineType#OKHTTP}) + */ public interface WebSocketEngineFactory { - WebSocketEngine create(WebSocketEngineConfig config); - EngineType getEngineType(); - static WebSocketEngineFactory getFirstAvailable() { WebSocketEngineFactory okWebSocketFactory = tryGetOkWebSocketFactory(); if (okWebSocketFactory != null) return okWebSocketFactory; @@ -28,8 +31,13 @@ static WebSocketEngineFactory tryGetDefaultFactory() { try { Class defaultFactoryClass = Class.forName("io.ably.lib.network.DefaultWebSocketEngineFactory"); return (WebSocketEngineFactory) defaultFactoryClass.getDeclaredConstructor().newInstance(); - } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) { + } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | NoSuchMethodException | + InvocationTargetException e) { return null; } } + + WebSocketEngine create(WebSocketEngineConfig config); + + EngineType getEngineType(); } diff --git a/network-client-core/src/main/java/io/ably/lib/network/WebSocketListener.java b/network-client-core/src/main/java/io/ably/lib/network/WebSocketListener.java index c3c223326..003d2a7bf 100644 --- a/network-client-core/src/main/java/io/ably/lib/network/WebSocketListener.java +++ b/network-client-core/src/main/java/io/ably/lib/network/WebSocketListener.java @@ -2,12 +2,56 @@ import java.nio.ByteBuffer; +/** + * WebSocket Listener + */ public interface WebSocketListener { + /** + * Called after an opening handshake has been performed and the given websocket is ready to be + * written on. + */ void onOpen(); + + /** + * Callback for binary messages received from the remote host + * + * @param blob The binary message that was received. + * @see #onMessage(String) + **/ void onMessage(ByteBuffer blob); + + /** + * Callback for string messages received from the remote host + * + * @param string The UTF-8 decoded message that was received. + * @see #onMessage(ByteBuffer) + **/ void onMessage(String string); + + /** + * Callback for receiving ping frame if it supported by websocket engine + */ void onWebsocketPing(); + + /** + * Called after the websocket connection has been closed. + * + * @param reason Additional information string + **/ void onClose(int code, String reason); + + /** + * Called when errors occurs. If an error causes the websocket connection to fail {@link + * WebSocketListener#onClose(int, String)} will be called additionally.
This method will be called + * primarily because of IO or protocol errors.
If the given exception is an RuntimeException + * that probably means that you encountered a bug.
+ * + * @param throwable The exception causing this error + **/ void onError(Throwable throwable); + + /** + * We invoke this callback when runtime is not able to use secure https algorithms (TLS 1.2 +) + */ void onOldJavaVersionDetected(Throwable throwable); }