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

Ws close #137

Merged
merged 4 commits into from
Sep 10, 2020
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
130 changes: 84 additions & 46 deletions app/src/main/java/com/github/gotify/service/WebSocketConnection.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import com.github.gotify.log.Log;
import java.util.Calendar;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import okhttp3.HttpUrl;
import okhttp3.OkHttpClient;
import okhttp3.Request;
Expand All @@ -21,6 +22,7 @@
import okhttp3.WebSocketListener;

class WebSocketConnection {
private static final AtomicLong ID = new AtomicLong(0);
private final ConnectivityManager connectivityManager;
private final AlarmManager alarmManager;
private OkHttpClient client;
Expand All @@ -38,7 +40,7 @@ class WebSocketConnection {
private BadRequestRunnable onBadRequest;
private OnNetworkFailureRunnable onNetworkFailure;
private Runnable onReconnected;
private boolean isClosed;
private State state;
private Runnable onDisconnect;

WebSocketConnection(
Expand Down Expand Up @@ -108,24 +110,33 @@ private Request request() {
}

public synchronized WebSocketConnection start() {
if (state == State.Connecting || state == State.Connected) {
return this;
}
close();
isClosed = false;
Log.i("WebSocket: starting...");
state = State.Connecting;
long nextId = ID.incrementAndGet();
Log.i("WebSocket(" + nextId + "): starting...");

webSocket = client.newWebSocket(request(), new Listener());
webSocket = client.newWebSocket(request(), new Listener(nextId));
return this;
}

public synchronized void close() {
if (webSocket != null) {
Log.i("WebSocket: closing existing connection.");
isClosed = true;
Log.i("WebSocket(" + ID.get() + "): closing existing connection.");
state = State.Disconnected;
webSocket.close(1000, "");
webSocket = null;
}
}

public synchronized void scheduleReconnect(long seconds) {
if (state == State.Connecting || state == State.Connected) {
return;
}
state = State.Scheduled;

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
Log.i(
"WebSocket: scheduling a restart in "
Expand All @@ -147,39 +158,49 @@ public synchronized void scheduleReconnect(long seconds) {
}

private class Listener extends WebSocketListener {
private final long id;

public Listener(long id) {
this.id = id;
}

@Override
public void onOpen(WebSocket webSocket, Response response) {
Log.i("WebSocket: opened");
synchronized (this) {
onOpen.run();

if (errorCount > 0) {
onReconnected.run();
errorCount = 0;
}
}
syncExec(
() -> {
state = State.Connected;
Log.i("WebSocket(" + id + "): opened");
onOpen.run();

if (errorCount > 0) {
onReconnected.run();
errorCount = 0;
}
});
super.onOpen(webSocket, response);
}

@Override
public void onMessage(WebSocket webSocket, String text) {
Log.i("WebSocket: received message " + text);
synchronized (this) {
Message message = Utils.JSON.fromJson(text, Message.class);
onMessage.onSuccess(message);
}
syncExec(
() -> {
Log.i("WebSocket(" + id + "): received message " + text);
Message message = Utils.JSON.fromJson(text, Message.class);
onMessage.onSuccess(message);
});
super.onMessage(webSocket, text);
}

@Override
public void onClosed(WebSocket webSocket, int code, String reason) {
synchronized (this) {
if (!isClosed) {
Log.w("WebSocket: closed");
onClose.run();
}
}
syncExec(
() -> {
if (state == State.Connected) {
Log.w("WebSocket(" + id + "): closed");
onClose.run();
}
state = State.Disconnected;
});

super.onClosed(webSocket, code, reason);
}
Expand All @@ -188,30 +209,40 @@ public void onClosed(WebSocket webSocket, int code, String reason) {
public void onFailure(WebSocket webSocket, Throwable t, Response response) {
String code = response != null ? "StatusCode: " + response.code() : "";
String message = response != null ? response.message() : "";
Log.e("WebSocket: failure " + code + " Message: " + message, t);
synchronized (this) {
if (response != null && response.code() >= 400 && response.code() <= 499) {
onBadRequest.execute(message);
close();
return;
}
Log.e("WebSocket(" + id + "): failure " + code + " Message: " + message, t);
syncExec(
() -> {
state = State.Disconnected;
if (response != null && response.code() >= 400 && response.code() <= 499) {
onBadRequest.execute(message);
close();
return;
}

errorCount++;

NetworkInfo network = connectivityManager.getActiveNetworkInfo();
if (network == null || !network.isConnected()) {
Log.i("WebSocket(" + id + "): Network not connected");
onDisconnect.run();
return;
}

int minutes = Math.min(errorCount * 2 - 1, 20);

onNetworkFailure.execute(minutes);
scheduleReconnect(TimeUnit.MINUTES.toSeconds(minutes));
});

errorCount++;
super.onFailure(webSocket, t, response);
}

NetworkInfo network = connectivityManager.getActiveNetworkInfo();
if (network == null || !network.isConnected()) {
Log.i("WebSocket: Network not connected");
onDisconnect.run();
return;
private void syncExec(Runnable runnable) {
synchronized (this) {
if (ID.get() == id) {
runnable.run();
}

int minutes = Math.min(errorCount * 2 - 1, 20);

onNetworkFailure.execute(minutes);
scheduleReconnect(TimeUnit.MINUTES.toSeconds(minutes));
}

super.onFailure(webSocket, t, response);
}
}

Expand All @@ -222,4 +253,11 @@ interface BadRequestRunnable {
interface OnNetworkFailureRunnable {
void execute(long millis);
}

enum State {
Scheduled,
Connecting,
Connected,
Disconnected
}
}
24 changes: 22 additions & 2 deletions app/src/main/java/com/github/gotify/service/WebSocketService.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@
import java.util.Map;
import java.util.concurrent.atomic.AtomicLong;

import static com.github.gotify.api.Callback.call;

public class WebSocketService extends Service {

public static final String NEW_MESSAGE_BROADCAST =
Expand Down Expand Up @@ -105,7 +107,7 @@ private void startPushService() {
cm,
alarmManager)
.onOpen(this::onOpen)
.onClose(() -> foreground(getString(R.string.websocket_closed)))
.onClose(this::onClose)
.onBadRequest(this::onBadRequest)
.onNetworkFailure(
(min) -> foreground(getString(R.string.websocket_failed, min)))
Expand All @@ -122,6 +124,24 @@ private void startPushService() {
picassoHandler.updateAppIds();
}

private void onClose() {
foreground(getString(R.string.websocket_closed_try_reconnect));
ClientFactory.userApiWithToken(settings)
.currentUser()
.enqueue(
call(
(ignored) -> this.doReconnect(),
(exception) -> {
if (exception.code() == 401) {
foreground(getString(R.string.websocket_closed_logout));
} else {
Log.i(
"WebSocket closed but the user still authenticated, trying to reconnect");
this.doReconnect();
}
}));
}

private void onDisconnect() {
foreground(getString(R.string.websocket_no_network));
}
Expand All @@ -131,7 +151,7 @@ private void doReconnect() {
return;
}

connection.scheduleReconnect(5);
connection.scheduleReconnect(15);
}

private void onBadRequest(String message) {
Expand Down
3 changes: 2 additions & 1 deletion app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@
<string name="auth_failed">Server returned 401 unauthorized while trying to get current user. This can be caused by deleting the client for this session.</string>
<string name="other_error">Could not get current user. Server (%s) responded with code %d: body (first 200 chars): %s</string>
<string name="websocket_failed">Connection failed, trying again in %d minutes</string>
<string name="websocket_closed">WebSocket closed; The client-token could be invalidated, please re-login</string>
<string name="websocket_closed_logout">User action required: Please login, the authentication token isn\'t valid anymore.</string>
<string name="websocket_closed_try_reconnect">Connection closed, trying to establish a new one.</string>
<string name="grouped_message">Received %d messages while being disconnected</string>
<string name="delete_all">Delete all</string>
<string name="delete_logs">Delete logs</string>
Expand Down