Added ping to age-out idle or stale connections.
Repushing due to a javadoc inherited from the master. Change-Id: I375fc114079ba7d0a2897791ba05fa41762db80b
Showing
2 changed files
with
95 additions
and
7 deletions
... | @@ -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) { |
143 | + if (connection.isOpen()) { | ||
105 | removeListeners(); | 144 | removeListeners(); |
106 | } | 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; | ||
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 { |
180 | + if (connection.isOpen()) { | ||
133 | connection.sendMessage(data.toString()); | 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 | ... | ... |
-
Please register or login to post a comment