Skip to content

Commit

Permalink
Refactoring WebSocket entry point
Browse files Browse the repository at this point in the history
  • Loading branch information
sashirestela committed Jan 23, 2025
1 parent 6f83dfc commit b759496
Show file tree
Hide file tree
Showing 7 changed files with 351 additions and 51 deletions.
101 changes: 71 additions & 30 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,12 @@ A Java library for making http client and websocket requests easily.
- [How to Use](#-how-to-use)
- [Installation](#-installation)
- [Features](#-features)
- [CleverClient Builder](#cleverclient-builder)
- [Http Client Options](#http-client-options)
- [WebSocket Options](#websocket-options)
- [CleverClient Creation](#cleverclient-creation)
- [Interface Annotations](#interface-annotations)
- [Supported Response Types](#supported-response-types)
- [Interface Default Methods](#interface-default-methods)
- [Exception Handling](#exception-handling)
- [WebSocket](#websocket)
- [Examples](#-examples)
- [Contributing](#-contributing)
- [License](#-license)
Expand Down Expand Up @@ -118,7 +117,7 @@ Take in account that you need to use **Java 11 or greater**.

## 📕 Features

### CleverClient Builder
### CleverClient Creation

We have the following attributes to create a CleverClient object:

Expand All @@ -138,6 +137,17 @@ We have the following attributes to create a CleverClient object:

```end(s)OfStream``` is required when you have endpoints sending back streams of data (Server Sent Events - SSE).

The attribute ```clientAdapter``` determines which Http client implementation to use. CleverClient supports two implementations out of the box:
- Java's HttpClient (default) via ```JavaHttpClientAdapter```
- Square's OkHttp via ```OkHttpClientAdapter```

| clientAdapter's value | Description |
|-------------------------------------------------|-------------------------------------|
| new JavaHttpClientAdapter() | Uses a default Java's HttpClient |
| new JavaHttpClientAdapter(customJavaHttpClient) | Uses a custom Java's HttpClient |
| new OkHttpClientAdapter() | Uses a default OkHttpClient |
| new OkHttpClientAdapter(customOkHttpClient) | Uses a custom OkHttpClient |

Example:

```java
Expand Down Expand Up @@ -185,32 +195,6 @@ var cleverClient = CleverClient.builder()
.build();
```

### Http Client Options

The Builder attribute ```clientAdapter``` determines which Http client implementation to use. CleverClient supports two implementations out of the box:
- Java's HttpClient (default) via ```JavaHttpClientAdapter```
- Square's OkHttp via ```OkHttpClientAdapter```

| clientAdapter's value | Description |
|-------------------------------------------------|-------------------------------------|
| new JavaHttpClientAdapter() | Uses a default Java's HttpClient |
| new JavaHttpClientAdapter(customJavaHttpClient) | Uses a custom Java's HttpClient |
| new OkHttpClientAdapter() | Uses a default OkHttpClient |
| new OkHttpClientAdapter(customOkHttpClient) | Uses a custom OkHttpClient |

### WebSocket Options

The Builder attribute ```webSocketAdapter``` lets you specify which WebSocket implementation to use. Similar to ```clientAdapter```, you can choose between:
- Java's HttpClient (default) via ```JavaHttpWebSocketAdapter```
- Square's OkHttp via ```OkHttpWebSocketAdapter```

| webSocketAdapter's value | Description |
|----------------------------------------------------|-------------------------------------|
| new JavaHttpWebSocketAdapter() | Uses a default Java's HttpClient |
| new JavaHttpWebSocketAdapter(customJavaHttpClient) | Uses a custom Java's HttpClient |
| new OkHttpWebSocketAdapter() | Uses a default OkHttpClient |
| new OkHttpWebSocketAdapter(customOkHttpClient) | Uses a custom OkHttpClient |

### Interface Annotations

| Annotation | Target | Attributes | Required Attrs | Mult |
Expand Down Expand Up @@ -366,6 +350,63 @@ try {

This mechanism allows you to handle both HTTP errors and other runtime exceptions in a clean, consistent way while preserving the original error information from the API response.

### WebSocket

We have the following attributes to create a CleverClient.WebSocket object:

| Attribute | Description | Required |
| -------------------|--------------------------------------------------------------|-----------|
| baseUrl | WebSocket's url | mandatory |
| queryParams | Map of query params (name/value) | optional |
| queryParam | Single query param as a name and a value | optional |
| headers | Map of headers (name/value) | optional |
| header | Single header as a name and a value | optional |
| webSocketAdapter | WebSocket implementation (Java HttpClient or OkHttp based) | optional |

The attribute ```webSocketAdapter``` lets you specify which WebSocket implementation to use. You can choose between:
- Java's HttpClient (default) via ```JavaHttpWebSocketAdapter```
- Square's OkHttp via ```OkHttpWebSocketAdapter```

| webSocketAdapter's value | Description |
|----------------------------------------------------|-------------------------------------|
| new JavaHttpWebSocketAdapter() | Uses a default Java's HttpClient |
| new JavaHttpWebSocketAdapter(customJavaHttpClient) | Uses a custom Java's HttpClient |
| new OkHttpWebSocketAdapter() | Uses a default OkHttpClient |
| new OkHttpWebSocketAdapter(customOkHttpClient) | Uses a custom OkHttpClient |

Example:

```java
final var BASE_URL = "ws://websocket.example.com";
final var HEADER_NAME = "Authorization";
final var HEADER_VALUE = "Bearer qwertyasdfghzxcvb";

var httpClient = HttpClient.newBuilder()
.version(Version.HTTP_1_1)
.followRedirects(Redirect.NORMAL)
.connectTimeout(Duration.ofSeconds(20))
.executor(Executors.newFixedThreadPool(3))
.proxy(ProxySelector.of(new InetSocketAddress("proxy.example.com", 80)))
.build();

var webSocket = CleverClient.WebSocket.builder()
.baseUrl(BASE_URL)
.queryParam("model", "qwerty_model")
.header(HEADER_NAME, HEADER_VALUE)
.webSocketAdapter(new JavaHttpWebSocketAdapter(httpClient))
.build();

webSocket.onOpen(() -> System.out.println("Connected"));
webSocket.onMessage(message -> System.out.println("Received: " + message));
webSocket.onClose((code, message) -> System.out.println("Closed"));

webSocket.connect().join();
webSocket.send("Hello World!").join();
webSocket.send("Welcome to the Jungle!").join();
webSocket.close();
```


## ✳ Examples

Some examples have been created in the folder [example](https://github.com/sashirestela/cleverclient/tree/main/src/example/java/io/github/sashirestela/cleverclient/example) and you can follow the next steps to execute them:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
package io.github.sashirestela.cleverclient.example;

import io.github.sashirestela.cleverclient.CleverClient;
import io.github.sashirestela.cleverclient.websocket.JavaHttpWebSocketAdapter;
import io.github.sashirestela.cleverclient.websocket.WebSocketAdapter;

import java.util.Map;

public class WebSocketExample {

protected WebSocketAdapter webSocketAdapter;
Expand All @@ -14,17 +13,21 @@ public WebSocketExample() {
}

public void run() {
final String BASE_URL = "wss://s13970.blr1.piesocket.com/v3/1?api_key=" + System.getenv("PIESOCKET_API_KEY")
+ "&notify_self=1";

webSocketAdapter.onOpen(() -> System.out.println("Connected"));
webSocketAdapter.onMessage(message -> System.out.println("Received: " + message));
webSocketAdapter.onClose((code, message) -> System.out.println("Closed"));

webSocketAdapter.connect(BASE_URL, Map.of()).join();
webSocketAdapter.send("Hello World!").join();
webSocketAdapter.send("Welcome to the Jungle!").join();
webSocketAdapter.close();
var webSocket = CleverClient.WebSocket.builder()
.baseUrl("wss://s13970.blr1.piesocket.com/v3/1")
.queryParam("api_key", System.getenv("PIESOCKET_API_KEY"))
.queryParam("notify_self", "1")
.webSockewAdapter(webSocketAdapter)
.build();

webSocket.onOpen(() -> System.out.println("Connected"));
webSocket.onMessage(message -> System.out.println("Received: " + message));
webSocket.onClose((code, message) -> System.out.println("Closed"));

webSocket.connect().join();
webSocket.send("Hello World!").join();
webSocket.send("Welcome to the Jungle!").join();
webSocket.close();
}

public static void main(String[] args) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
import io.github.sashirestela.cleverclient.http.HttpResponseData;
import io.github.sashirestela.cleverclient.support.Configurator;
import io.github.sashirestela.cleverclient.util.CommonUtil;
import io.github.sashirestela.cleverclient.websocket.Action;
import io.github.sashirestela.cleverclient.websocket.JavaHttpWebSocketAdapter;
import io.github.sashirestela.cleverclient.websocket.WebSocketAdapter;
import lombok.Builder;
import lombok.Getter;
Expand All @@ -20,6 +22,8 @@
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.UnaryOperator;

Expand All @@ -38,7 +42,6 @@ public class CleverClient {
private final UnaryOperator<HttpRequestData> requestInterceptor;
private final UnaryOperator<HttpResponseData> responseInterceptor;
private final HttpClientAdapter clientAdapter;
private final WebSocketAdapter webSockewAdapter;
private final HttpProcessor httpProcessor;

/**
Expand All @@ -51,8 +54,6 @@ public class CleverClient {
* @param responseInterceptor Function to modify the response once it has been received.
* @param clientAdapter Component to call http services. If none is passed the
* JavaHttpClientAdapter will be used. Optional.
* @param webSocketAdapter Component to do web socket interactions. If none is passed the
* JavaHttpWebSocketAdapter will be used. Optional.
* @param endsOfStream Texts used to mark the final of streams when handling server sent
* events (SSE). Optional.
* @param objectMapper Provides Json conversions either to and from objects. Optional.
Expand All @@ -61,8 +62,8 @@ public class CleverClient {
@SuppressWarnings("java:S107")
public CleverClient(@NonNull String baseUrl, @Singular Map<String, String> headers, Consumer<Object> bodyInspector,
UnaryOperator<HttpRequestData> requestInterceptor, UnaryOperator<HttpResponseData> responseInterceptor,
HttpClientAdapter clientAdapter, WebSocketAdapter webSocketAdapter,
@Singular("endOfStream") List<String> endsOfStream, ObjectMapper objectMapper) {
HttpClientAdapter clientAdapter, @Singular("endOfStream") List<String> endsOfStream,
ObjectMapper objectMapper) {
this.baseUrl = baseUrl;
this.headers = Optional.ofNullable(headers).orElse(Map.of());
this.bodyInspector = bodyInspector;
Expand All @@ -71,7 +72,6 @@ public CleverClient(@NonNull String baseUrl, @Singular Map<String, String> heade
this.clientAdapter = Optional.ofNullable(clientAdapter).orElse(new JavaHttpClientAdapter());
this.clientAdapter.setRequestInterceptor(this.requestInterceptor);
this.clientAdapter.setResponseInterceptor(this.responseInterceptor);
this.webSockewAdapter = webSocketAdapter;

this.httpProcessor = HttpProcessor.builder()
.baseUrl(this.baseUrl)
Expand All @@ -98,4 +98,69 @@ public <T> T create(Class<T> interfaceClass) {
return this.httpProcessor.createProxy(interfaceClass);
}

/**
* Handles websocket communication.
*/
@Getter
public static class WebSocket {

private final String baseUrl;
private final Map<String, String> queryParams;
private final Map<String, String> headers;
private final WebSocketAdapter webSockewAdapter;
private String fullUrl;

/**
* Constructor to create an instance of CleverClient.WebSocket
*
* @param baseUrl Root of the url of the WebSocket to call. Mandatory.
* @param queryParams Query parameters (key=value) to be added to the baseUrl.
* @param headers Http headers to be passed to the WebSocket. Optional.
* @param webSockewAdapter Component to do web socket interactions. If none is passed the
* JavaHttpWebSocketAdapter will be used. Optional.
*/
@Builder
public WebSocket(@NonNull String baseUrl, @Singular Map<String, String> queryParams,
@Singular Map<String, String> headers, WebSocketAdapter webSockewAdapter) {
this.baseUrl = baseUrl;
this.queryParams = Optional.ofNullable(queryParams).orElse(Map.of());
this.headers = Optional.ofNullable(headers).orElse(Map.of());
this.webSockewAdapter = Optional.ofNullable(webSockewAdapter).orElse(new JavaHttpWebSocketAdapter());
this.fullUrl = buildFullUrl();
}

private String buildFullUrl() {
return baseUrl + CommonUtil.stringMapToUrl(queryParams);
}

public CompletableFuture<Void> connect() {
return webSockewAdapter.connect(fullUrl, headers);
}

public CompletableFuture<Void> send(String message) {
return webSockewAdapter.send(message);
}

public void close() {
webSockewAdapter.close();
}

public void onMessage(Consumer<String> callback) {
webSockewAdapter.onMessage(callback);
}

public void onOpen(Action callback) {
webSockewAdapter.onOpen(callback);
}

public void onClose(BiConsumer<Integer, String> callback) {
webSockewAdapter.onClose(callback);
}

public void onError(Consumer<Throwable> callback) {
webSockewAdapter.onError(callback);
}

}

}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package io.github.sashirestela.cleverclient.util;

import java.lang.reflect.Array;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
Expand Down Expand Up @@ -75,4 +77,20 @@ public static Map<String, String> listToMapOfString(List<String> list) {
return createMapString(array);
}

public static String stringMapToUrl(Map<String, String> stringMap) {
if (stringMap.isEmpty()) {
return "";
}
var stringUrl = new StringBuilder();
stringMap.forEach((key, value) -> {
if (value != null) {
stringUrl.append(stringUrl.length() == 0 ? "?" : "&")
.append(URLEncoder.encode(key, StandardCharsets.UTF_8))
.append("=")
.append(URLEncoder.encode(value, StandardCharsets.UTF_8));
}
});
return stringUrl.toString();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ public void onClose(BiConsumer<Integer, String> callback) {
this.closeCallback = callback;
}

public void onError(Consumer<Throwable> errorCallback) {
this.errorCallback = errorCallback;
public void onError(Consumer<Throwable> callback) {
this.errorCallback = callback;
}

}
Loading

0 comments on commit b759496

Please sign in to comment.