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
97 additions
and
9 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) { |
| 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 | ... | ... |
-
Please register or login to post a comment