Skip to content

Commit

Permalink
feat: OkHttp implementation for making HTTP calls and WebSocket conne…
Browse files Browse the repository at this point in the history
…ctions
  • Loading branch information
ttypic committed Sep 25, 2024
1 parent 6f38c17 commit e53b6e7
Show file tree
Hide file tree
Showing 13 changed files with 363 additions and 1 deletion.
70 changes: 70 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -500,6 +500,76 @@ realtime.setAndroidContext(context);
realtime.push.activate();
```

## Using Ably SDK Under a Proxy

When working in environments where outbound internet access is restricted, such as behind a corporate proxy, the Ably SDK allows you to configure a proxy server for HTTP and WebSocket connections.

### Add the Required Dependency

You need to use **OkHttp** library for making HTTP calls and WebSocket connections in the Ably SDK to get proxy support both for your Rest and Realtime clients.

Add the following dependency to your `build.gradle` file:

```groovy
dependencies {
runtimeOnly("io.ably:network-client-okhttp:1.2.43")
}
```

### Configure Proxy Settings

After adding the required OkHttp dependency, you need to configure the proxy settings for your Ably client. This can be done by setting the proxy options in the `ClientOptions` object when you instantiate the Ably SDK.

Here’s an example of how to configure and use a proxy:

#### Java Example

```java
import io.ably.lib.realtime.AblyRealtime;
import io.ably.lib.rest.AblyRest;
import io.ably.lib.transport.Defaults;
import io.ably.lib.types.ClientOptions;
import io.ably.lib.types.ProxyOptions;
import io.ably.lib.http.HttpAuth;

public class AblyWithProxy {
public static void main(String[] args) throws Exception {
// Configure Ably Client options
ClientOptions options = new ClientOptions();

// Setup proxy settings
ProxyOptions proxy = new ProxyOptions();
proxy.host = "your-proxy-host"; // Replace with your proxy host
proxy.port = 8080; // Replace with your proxy port

// Optional: If the proxy requires authentication
proxy.username = "your-username"; // Replace with proxy username
proxy.password = "your-password"; // Replace with proxy password
proxy.prefAuthType = HttpAuth.Type.BASIC; // Choose your preferred authentication type (e.g., BASIC or DIGEST)

// Attach the proxy settings to the client options
options.proxy = proxy;

// Create an instance of Ably using the configured options
AblyRest ably = new AblyRest(options);

// Alternatively, for real-time connections
AblyRealtime ablyRealtime = new AblyRealtime(options);

// Use the Ably client as usual
}
}
```

### Additional Resources

- [Ably Java SDK Documentation](https://ably.com/documentation)
- [OkHttp Documentation](https://square.github.io/okhttp/)
- [Proxy Server Wikipedia](https://en.wikipedia.org/wiki/Proxy_server)

---

By following the steps in this guide, you should be able to configure the Ably SDK to work seamlessly behind a proxy using OkHttp for network requests.
## Resources

Visit https://www.ably.com/docs for a complete API reference and more examples.
Expand Down
2 changes: 2 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ dexmaker = "1.4"
android-retrostreams = "1.7.4"
maven-publish = "0.29.0"
lombok = "8.10"
okhttp = "4.12.0"

[libraries]
gson = { group = "com.google.code.gson", name = "gson", version.ref = "gson" }
Expand All @@ -38,6 +39,7 @@ dexmaker = { group = "com.crittercism.dexmaker", name = "dexmaker", version.ref
dexmaker-dx = { group = "com.crittercism.dexmaker", name = "dexmaker-dx", version.ref = "dexmaker" }
dexmaker-mockito = { group = "com.crittercism.dexmaker", name = "dexmaker-mockito", version.ref = "dexmaker" }
android-retrostreams = { group = "net.sourceforge.streamsupport", name = "android-retrostreams", version.ref = "android-retrostreams" }
okhttp = { group ="com.squareup.okhttp3", name = "okhttp", version.ref = "okhttp" }

[bundles]
common = ["msgpack", "vcdiff-core"]
Expand Down
2 changes: 1 addition & 1 deletion network-client-default/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@ java {
}

dependencies {
api(project(":network-client-core"))
implementation(project(":network-client-core"))
implementation(libs.java.websocket)
}
15 changes: 15 additions & 0 deletions network-client-okhttp/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
plugins {
`java-library`
alias(libs.plugins.lombok)
alias(libs.plugins.maven.publish)
}

java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}

dependencies {
implementation(project(":network-client-core"))
implementation(libs.okhttp)
}
4 changes: 4 additions & 0 deletions network-client-okhttp/gradle.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
POM_ARTIFACT_ID=network-client-okhttp
POM_NAME=Default HTTP client
POM_DESCRIPTION=Default implementation for HTTP client
POM_PACKAGING=jar
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package io.ably.lib.network;

import okhttp3.Call;
import okhttp3.Response;

import java.io.IOException;
import java.net.ConnectException;
import java.net.NoRouteToHostException;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;

public class OkHttpCall implements HttpCall {
private final Call call;

public OkHttpCall(Call call) {
this.call = call;
}

@Override
public HttpResponse execute() {
try (Response response = call.execute()) {
return HttpResponse.builder()
.headers(response.headers().toMultimap())
.code(response.code())
.message(response.message())
.body(
response.body() != null && response.body().contentType() != null
? new HttpBody(response.body().contentType().toString(), response.body().bytes())
: null
)
.build();

} catch (ConnectException | SocketTimeoutException | UnknownHostException | NoRouteToHostException fce) {
throw new FailedConnectionException(fce);
} catch (IOException ioe) {
throw new RuntimeException(ioe);
}

}

@Override
public void cancel() {
call.cancel();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package io.ably.lib.network;

import okhttp3.Call;
import okhttp3.OkHttpClient;

import java.util.concurrent.TimeUnit;

public class OkHttpEngine implements HttpEngine {

private final OkHttpClient client;
private final HttpEngineConfig config;

public OkHttpEngine(OkHttpClient client, HttpEngineConfig config) {
this.client = client;
this.config = config;
}

@Override
public HttpCall call(HttpRequest request) {
Call call = client.newBuilder()
.connectTimeout(request.getHttpOpenTimeout(), TimeUnit.MILLISECONDS)
.readTimeout(request.getHttpReadTimeout(), TimeUnit.MILLISECONDS)
.build()
.newCall(OkHttpUtils.toOkhttpRequest(request));
return new OkHttpCall(call);
}

@Override
public boolean isUsingProxy() {
return config.getProxy() != null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package io.ably.lib.network;

import okhttp3.OkHttpClient;

public class OkHttpEngineFactory implements HttpEngineFactory {
@Override
public HttpEngine create(HttpEngineConfig config) {
OkHttpClient.Builder connectionBuilder = new OkHttpClient.Builder();
OkHttpUtils.injectProxySetting(config.getProxy(), connectionBuilder);
return new OkHttpEngine(connectionBuilder.build(), config);
}

@Override
public EngineType getEngineType() {
return EngineType.OKHTTP;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package io.ably.lib.network;

import okhttp3.Credentials;
import okhttp3.Headers;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;

import java.net.InetSocketAddress;
import java.net.Proxy;
import java.util.List;
import java.util.Map;

public class OkHttpUtils {
public static void injectProxySetting(ProxyConfig proxyConfig, OkHttpClient.Builder connectionBuilder) {
if (proxyConfig != null) return;
connectionBuilder.proxy(new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyConfig.getHost(), proxyConfig.getPort())));
if (proxyConfig.getUsername() == null || proxyConfig.getAuthType() != ProxyAuthType.BASIC) return;
String username = proxyConfig.getUsername();
String password = proxyConfig.getPassword();
connectionBuilder.proxyAuthenticator((route, response) -> {
String credential = Credentials.basic(username, password);
return response.request().newBuilder()
.header("Proxy-Authorization", credential)
.build();
});
}

public static Request toOkhttpRequest(HttpRequest request) {
Request.Builder builder = new Request.Builder()
.url(request.getUrl());

RequestBody body = null;

if (request.getBody() != null) {
body = RequestBody.create(request.getBody().getContent(), MediaType.parse(request.getBody().getContentType()));
}

builder.method(request.getMethod(), body);
for (Map.Entry<String, List<String>> entry : request.getHeaders().entrySet()) {
String headerName = entry.getKey();
List<String> values = entry.getValue();
for (String headerValue : values) {
builder.addHeader(headerName, headerValue);
}
}

return builder.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package io.ably.lib.network;

import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.WebSocket;
import okio.ByteString;

public class OkHttpWebSocketClient implements WebSocketClient {
private final OkHttpClient connection;
private final Request request;
private final WebSocketListener listener;
private WebSocket webSocket;

public OkHttpWebSocketClient(OkHttpClient connection, Request request, WebSocketListener listener) {
this.connection = connection;
this.request = request;
this.listener = listener;
}

@Override
public void connect() {
webSocket = connection.newWebSocket(request, new WebSocketHandler(listener));
}

@Override
public void close() {
webSocket.close(1000, "Close");
}

@Override
public void close(int code, String reason) {
webSocket.close(code, reason);
}

@Override
public void cancel(int code, String reason) {
webSocket.cancel();
listener.onClose(code, reason);
}

@Override
public void send(byte[] bytes) {
webSocket.send(ByteString.of(bytes));
}

@Override
public void send(String message) {
webSocket.send(message);
}

private static class WebSocketHandler extends okhttp3.WebSocketListener {
private final WebSocketListener listener;

private WebSocketHandler(WebSocketListener listener) {
super();
this.listener = listener;
}

@Override
public void onClosed(WebSocket webSocket, int code, String reason) {
listener.onClose(code, reason);
}

@Override
public void onFailure(WebSocket webSocket, Throwable t, Response response) {
listener.onError(t);
}

@Override
public void onMessage(WebSocket webSocket, String text) {
listener.onMessage(text);
}

@Override
public void onMessage(WebSocket webSocket, ByteString bytes) {
listener.onMessage(bytes.asByteBuffer());
}

@Override
public void onOpen(WebSocket webSocket, Response response) {
listener.onOpen();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package io.ably.lib.network;

import okhttp3.OkHttpClient;
import okhttp3.Request;

public class OkHttpWebSocketEngine implements WebSocketEngine {
private final WebSocketEngineConfig config;

public OkHttpWebSocketEngine(WebSocketEngineConfig config) {
this.config = config;
}

@Override
public WebSocketClient create(String url, WebSocketListener listener) {
OkHttpClient.Builder connectionBuilder = new OkHttpClient.Builder();

Request.Builder requestBuilder = new Request.Builder().url(url);

OkHttpUtils.injectProxySetting(config.getProxy(), connectionBuilder);

if (config.getSslSocketFactory() != null) {
connectionBuilder.sslSocketFactory(config.getSslSocketFactory());
}

return new OkHttpWebSocketClient(connectionBuilder.build(), requestBuilder.build(), listener);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package io.ably.lib.network;

public class OkHttpWebSocketEngineFactory implements WebSocketEngineFactory {
@Override
public WebSocketEngine create(WebSocketEngineConfig config) {
return new OkHttpWebSocketEngine(config);
}

@Override
public EngineType getEngineType() {
return EngineType.OKHTTP;
}
}
Loading

0 comments on commit e53b6e7

Please sign in to comment.