Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Release 2.1.0 #105

Merged
merged 4 commits into from
Jan 23, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<groupId>io.github.sashirestela</groupId>
<artifactId>cleverclient</artifactId>
<version>2.0.0</version>
<version>2.1.0</version>
<packaging>jar</packaging>

<name>cleverclient</name>
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,7 @@
package io.github.sashirestela.cleverclient.support;

import com.fasterxml.jackson.databind.ObjectMapper;
import io.github.sashirestela.cleverclient.util.JsonUtil;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
Expand Down Expand Up @@ -29,6 +30,7 @@ public Configurator(@Singular("endOfStream") List<String> endsOfStream, ObjectMa
}
configurator.endsOfStream = endsOfStream;
configurator.objectMapper = objectMapper;
JsonUtil.updateObjectMapper(objectMapper);
configurator.wasBuilt = true;
}

Expand Down
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();
}

}
Loading
Loading