Added ping to age-out idle or stale connections.
Repushing due to a javadoc inherited from the master.
Change-Id: I375fc114079ba7d0a2897791ba05fa41762db80b
diff --git a/web/gui/src/main/java/org/onlab/onos/gui/GuiWebSocketServlet.java b/web/gui/src/main/java/org/onlab/onos/gui/GuiWebSocketServlet.java
index 02b46ed..aa00a863 100644
--- a/web/gui/src/main/java/org/onlab/onos/gui/GuiWebSocketServlet.java
+++ b/web/gui/src/main/java/org/onlab/onos/gui/GuiWebSocketServlet.java
@@ -20,19 +20,56 @@
import org.onlab.osgi.DefaultServiceDirectory;
import org.onlab.osgi.ServiceDirectory;
+import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+import java.util.Timer;
+import java.util.TimerTask;
/**
* Web socket servlet capable of creating various sockets for the user interface.
*/
public class GuiWebSocketServlet extends WebSocketServlet {
+ private static final long PING_DELAY_MS = 5000;
+
private ServiceDirectory directory = new DefaultServiceDirectory();
+ private final Set<TopologyWebSocket> sockets = new HashSet<>();
+ private final Timer timer = new Timer();
+ private final TimerTask pruner = new Pruner();
+
+ @Override
+ public void init() throws ServletException {
+ super.init();
+ timer.schedule(pruner, PING_DELAY_MS, PING_DELAY_MS);
+ }
+
@Override
public WebSocket doWebSocketConnect(HttpServletRequest request, String protocol) {
-
- return new TopologyWebSocket(directory);
+ TopologyWebSocket socket = new TopologyWebSocket(directory);
+ synchronized (sockets) {
+ sockets.add(socket);
+ }
+ return socket;
}
+ // Task for pruning web-sockets that are idle.
+ private class Pruner extends TimerTask {
+ @Override
+ public void run() {
+ synchronized (sockets) {
+ Iterator<TopologyWebSocket> it = sockets.iterator();
+ while (it.hasNext()) {
+ TopologyWebSocket socket = it.next();
+ if (socket.isIdle()) {
+ it.remove();
+ socket.close();
+ }
+ }
+ }
+ }
+ }
}
diff --git a/web/gui/src/main/java/org/onlab/onos/gui/TopologyWebSocket.java b/web/gui/src/main/java/org/onlab/onos/gui/TopologyWebSocket.java
index 2506b13..e4bfccd 100644
--- a/web/gui/src/main/java/org/onlab/onos/gui/TopologyWebSocket.java
+++ b/web/gui/src/main/java/org/onlab/onos/gui/TopologyWebSocket.java
@@ -61,13 +61,21 @@
* Web socket capable of interacting with the GUI topology view.
*/
public class TopologyWebSocket
- extends TopologyMessages implements WebSocket.OnTextMessage {
+ extends TopologyMessages
+ implements WebSocket.OnTextMessage, WebSocket.OnControl {
+
+ private static final long MAX_AGE_MS = 15000;
+
+ private static final byte PING = 0x9;
+ private static final byte PONG = 0xA;
+ private static final byte[] PING_DATA = new byte[]{(byte) 0xde, (byte) 0xad};
private static final String APP_ID = "org.onlab.onos.gui";
private final ApplicationId appId;
private Connection connection;
+ private FrameConnection control;
private final ClusterEventListener clusterListener = new InternalClusterListener();
private final DeviceListener deviceListener = new InternalDeviceListener();
@@ -79,6 +87,8 @@
// Intents that are being monitored for the GUI
private static Map<IntentId, Long> intentsToMonitor = new ConcurrentHashMap<>();
+ private long lastActive = System.currentTimeMillis();
+
/**
* Creates a new web-socket for serving data to GUI topology view.
*
@@ -89,9 +99,37 @@
appId = directory.get(CoreService.class).registerApplication(APP_ID);
}
+ /**
+ * Issues a close on the connection.
+ */
+ synchronized void close() {
+ if (connection.isOpen()) {
+ removeListeners();
+ connection.close();
+ }
+ }
+
+ /**
+ * Indicates if this connection is idle.
+ */
+ synchronized boolean isIdle() {
+ boolean idle = (System.currentTimeMillis() - lastActive) > MAX_AGE_MS;
+ if (idle || !connection.isOpen()) {
+ return true;
+ }
+ try {
+ control.sendControl(PING, PING_DATA, 0, PING_DATA.length);
+ } catch (IOException e) {
+ log.warn("Unable to send ping message due to: ", e);
+ }
+ return false;
+ }
+
@Override
public void onOpen(Connection connection) {
+ log.info("GUI client connected");
this.connection = connection;
+ this.control = (FrameConnection) connection;
addListeners();
sendAllInstances();
@@ -101,12 +139,22 @@
}
@Override
- public void onClose(int closeCode, String message) {
- removeListeners();
+ public synchronized void onClose(int closeCode, String message) {
+ if (connection.isOpen()) {
+ removeListeners();
+ }
+ log.info("GUI client disconnected");
+ }
+
+ @Override
+ public boolean onControl(byte controlCode, byte[] data, int offset, int length) {
+ lastActive = System.currentTimeMillis();
+ return true;
}
@Override
public void onMessage(String data) {
+ lastActive = System.currentTimeMillis();
try {
ObjectNode event = (ObjectNode) mapper.reader().readTree(data);
String type = string(event, "event", "unknown");
@@ -123,16 +171,17 @@
}
} catch (Exception e) {
log.warn("Unable to parse GUI request {} due to {}", data, e);
- e.printStackTrace();
}
}
// Sends the specified data to the client.
- private void sendMessage(ObjectNode data) {
+ private synchronized void sendMessage(ObjectNode data) {
try {
- connection.sendMessage(data.toString());
+ if (connection.isOpen()) {
+ connection.sendMessage(data.toString());
+ }
} catch (IOException e) {
- e.printStackTrace();
+ log.warn("Unable to send message {} to GUI due to {}", data, e);
}
}
@@ -229,6 +278,7 @@
linkService.removeListener(linkListener);
hostService.removeListener(hostListener);
mastershipService.removeListener(mastershipListener);
+ intentService.removeListener(intentListener);
}
// Cluster event listener.
@@ -268,6 +318,7 @@
@Override
public void event(MastershipEvent event) {
// TODO: Is DeviceEvent.Type.DEVICE_MASTERSHIP_CHANGED the same?
+
}
}