Thomas Vachuska

Added ping to age-out idle or stale connections.

Repushing due to a javadoc inherited from the master.

Change-Id: I375fc114079ba7d0a2897791ba05fa41762db80b
...@@ -20,19 +20,56 @@ import org.eclipse.jetty.websocket.WebSocketServlet; ...@@ -20,19 +20,56 @@ import org.eclipse.jetty.websocket.WebSocketServlet;
20 import org.onlab.osgi.DefaultServiceDirectory; 20 import org.onlab.osgi.DefaultServiceDirectory;
21 import org.onlab.osgi.ServiceDirectory; 21 import org.onlab.osgi.ServiceDirectory;
22 22
23 +import javax.servlet.ServletException;
23 import javax.servlet.http.HttpServletRequest; 24 import javax.servlet.http.HttpServletRequest;
25 +import java.util.HashSet;
26 +import java.util.Iterator;
27 +import java.util.Set;
28 +import java.util.Timer;
29 +import java.util.TimerTask;
24 30
25 /** 31 /**
26 * Web socket servlet capable of creating various sockets for the user interface. 32 * Web socket servlet capable of creating various sockets for the user interface.
27 */ 33 */
28 public class GuiWebSocketServlet extends WebSocketServlet { 34 public class GuiWebSocketServlet extends WebSocketServlet {
29 35
36 + private static final long PING_DELAY_MS = 5000;
37 +
30 private ServiceDirectory directory = new DefaultServiceDirectory(); 38 private ServiceDirectory directory = new DefaultServiceDirectory();
31 39
40 + private final Set<TopologyWebSocket> sockets = new HashSet<>();
41 + private final Timer timer = new Timer();
42 + private final TimerTask pruner = new Pruner();
43 +
32 @Override 44 @Override
33 - public WebSocket doWebSocketConnect(HttpServletRequest request, String protocol) { 45 + public void init() throws ServletException {
46 + super.init();
47 + timer.schedule(pruner, PING_DELAY_MS, PING_DELAY_MS);
48 + }
34 49
35 - return new TopologyWebSocket(directory); 50 + @Override
51 + public WebSocket doWebSocketConnect(HttpServletRequest request, String protocol) {
52 + TopologyWebSocket socket = new TopologyWebSocket(directory);
53 + synchronized (sockets) {
54 + sockets.add(socket);
55 + }
56 + return socket;
36 } 57 }
37 58
59 + // Task for pruning web-sockets that are idle.
60 + private class Pruner extends TimerTask {
61 + @Override
62 + public void run() {
63 + synchronized (sockets) {
64 + Iterator<TopologyWebSocket> it = sockets.iterator();
65 + while (it.hasNext()) {
66 + TopologyWebSocket socket = it.next();
67 + if (socket.isIdle()) {
68 + it.remove();
69 + socket.close();
70 + }
71 + }
72 + }
73 + }
74 + }
38 } 75 }
......
...@@ -61,13 +61,21 @@ import static org.onlab.onos.net.link.LinkEvent.Type.LINK_ADDED; ...@@ -61,13 +61,21 @@ import static org.onlab.onos.net.link.LinkEvent.Type.LINK_ADDED;
61 * Web socket capable of interacting with the GUI topology view. 61 * Web socket capable of interacting with the GUI topology view.
62 */ 62 */
63 public class TopologyWebSocket 63 public class TopologyWebSocket
64 - extends TopologyMessages implements WebSocket.OnTextMessage { 64 + extends TopologyMessages
65 + implements WebSocket.OnTextMessage, WebSocket.OnControl {
66 +
67 + private static final long MAX_AGE_MS = 15000;
68 +
69 + private static final byte PING = 0x9;
70 + private static final byte PONG = 0xA;
71 + private static final byte[] PING_DATA = new byte[]{(byte) 0xde, (byte) 0xad};
65 72
66 private static final String APP_ID = "org.onlab.onos.gui"; 73 private static final String APP_ID = "org.onlab.onos.gui";
67 74
68 private final ApplicationId appId; 75 private final ApplicationId appId;
69 76
70 private Connection connection; 77 private Connection connection;
78 + private FrameConnection control;
71 79
72 private final ClusterEventListener clusterListener = new InternalClusterListener(); 80 private final ClusterEventListener clusterListener = new InternalClusterListener();
73 private final DeviceListener deviceListener = new InternalDeviceListener(); 81 private final DeviceListener deviceListener = new InternalDeviceListener();
...@@ -79,6 +87,8 @@ public class TopologyWebSocket ...@@ -79,6 +87,8 @@ public class TopologyWebSocket
79 // Intents that are being monitored for the GUI 87 // Intents that are being monitored for the GUI
80 private static Map<IntentId, Long> intentsToMonitor = new ConcurrentHashMap<>(); 88 private static Map<IntentId, Long> intentsToMonitor = new ConcurrentHashMap<>();
81 89
90 + private long lastActive = System.currentTimeMillis();
91 +
82 /** 92 /**
83 * Creates a new web-socket for serving data to GUI topology view. 93 * Creates a new web-socket for serving data to GUI topology view.
84 * 94 *
...@@ -89,9 +99,37 @@ public class TopologyWebSocket ...@@ -89,9 +99,37 @@ public class TopologyWebSocket
89 appId = directory.get(CoreService.class).registerApplication(APP_ID); 99 appId = directory.get(CoreService.class).registerApplication(APP_ID);
90 } 100 }
91 101
102 + /**
103 + * Issues a close on the connection.
104 + */
105 + synchronized void close() {
106 + if (connection.isOpen()) {
107 + removeListeners();
108 + connection.close();
109 + }
110 + }
111 +
112 + /**
113 + * Indicates if this connection is idle.
114 + */
115 + synchronized boolean isIdle() {
116 + boolean idle = (System.currentTimeMillis() - lastActive) > MAX_AGE_MS;
117 + if (idle || !connection.isOpen()) {
118 + return true;
119 + }
120 + try {
121 + control.sendControl(PING, PING_DATA, 0, PING_DATA.length);
122 + } catch (IOException e) {
123 + log.warn("Unable to send ping message due to: ", e);
124 + }
125 + return false;
126 + }
127 +
92 @Override 128 @Override
93 public void onOpen(Connection connection) { 129 public void onOpen(Connection connection) {
130 + log.info("GUI client connected");
94 this.connection = connection; 131 this.connection = connection;
132 + this.control = (FrameConnection) connection;
95 addListeners(); 133 addListeners();
96 134
97 sendAllInstances(); 135 sendAllInstances();
...@@ -101,12 +139,22 @@ public class TopologyWebSocket ...@@ -101,12 +139,22 @@ public class TopologyWebSocket
101 } 139 }
102 140
103 @Override 141 @Override
104 - public void onClose(int closeCode, String message) { 142 + public synchronized void onClose(int closeCode, String message) {
105 - removeListeners(); 143 + if (connection.isOpen()) {
144 + removeListeners();
145 + }
146 + log.info("GUI client disconnected");
147 + }
148 +
149 + @Override
150 + public boolean onControl(byte controlCode, byte[] data, int offset, int length) {
151 + lastActive = System.currentTimeMillis();
152 + return true;
106 } 153 }
107 154
108 @Override 155 @Override
109 public void onMessage(String data) { 156 public void onMessage(String data) {
157 + lastActive = System.currentTimeMillis();
110 try { 158 try {
111 ObjectNode event = (ObjectNode) mapper.reader().readTree(data); 159 ObjectNode event = (ObjectNode) mapper.reader().readTree(data);
112 String type = string(event, "event", "unknown"); 160 String type = string(event, "event", "unknown");
...@@ -123,16 +171,17 @@ public class TopologyWebSocket ...@@ -123,16 +171,17 @@ public class TopologyWebSocket
123 } 171 }
124 } catch (Exception e) { 172 } catch (Exception e) {
125 log.warn("Unable to parse GUI request {} due to {}", data, e); 173 log.warn("Unable to parse GUI request {} due to {}", data, e);
126 - e.printStackTrace();
127 } 174 }
128 } 175 }
129 176
130 // Sends the specified data to the client. 177 // Sends the specified data to the client.
131 - private void sendMessage(ObjectNode data) { 178 + private synchronized void sendMessage(ObjectNode data) {
132 try { 179 try {
133 - connection.sendMessage(data.toString()); 180 + if (connection.isOpen()) {
181 + connection.sendMessage(data.toString());
182 + }
134 } catch (IOException e) { 183 } catch (IOException e) {
135 - e.printStackTrace(); 184 + log.warn("Unable to send message {} to GUI due to {}", data, e);
136 } 185 }
137 } 186 }
138 187
...@@ -229,6 +278,7 @@ public class TopologyWebSocket ...@@ -229,6 +278,7 @@ public class TopologyWebSocket
229 linkService.removeListener(linkListener); 278 linkService.removeListener(linkListener);
230 hostService.removeListener(hostListener); 279 hostService.removeListener(hostListener);
231 mastershipService.removeListener(mastershipListener); 280 mastershipService.removeListener(mastershipListener);
281 + intentService.removeListener(intentListener);
232 } 282 }
233 283
234 // Cluster event listener. 284 // Cluster event listener.
...@@ -268,6 +318,7 @@ public class TopologyWebSocket ...@@ -268,6 +318,7 @@ public class TopologyWebSocket
268 @Override 318 @Override
269 public void event(MastershipEvent event) { 319 public void event(MastershipEvent event) {
270 // TODO: Is DeviceEvent.Type.DEVICE_MASTERSHIP_CHANGED the same? 320 // TODO: Is DeviceEvent.Type.DEVICE_MASTERSHIP_CHANGED the same?
321 +
271 } 322 }
272 } 323 }
273 324
......