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); + + /** + * @returntrue
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..d3c536ec4 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,16 @@
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})
+ *
+ * Please note that all methods in HttpEngineFactory
are static.
+ */
public interface HttpEngineFactory {
- HttpEngine create(HttpEngineConfig config);
- EngineType getEngineType();
-
static HttpEngineFactory getFirstAvailable() {
HttpEngineFactory okHttpFactory = tryGetOkHttpFactory();
if (okHttpFactory != null) return okHttpFactory;
@@ -19,7 +24,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 +34,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..276229d9f 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,15 @@
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})
+ *
+ * Please note that all methods in WebSocketEngineFactory
are static.
+ */
public interface WebSocketEngineFactory {
- WebSocketEngine create(WebSocketEngineConfig config);
- EngineType getEngineType();
-
static WebSocketEngineFactory getFirstAvailable() {
WebSocketEngineFactory okWebSocketFactory = tryGetOkWebSocketFactory();
if (okWebSocketFactory != null) return okWebSocketFactory;
@@ -28,8 +33,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);
}