|
10 | 10 |
|
11 | 11 | from tornado import gen, web |
12 | 12 | from tornado.concurrent import Future |
13 | | -from tornado.ioloop import IOLoop |
| 13 | +from tornado.ioloop import IOLoop, PeriodicCallback |
14 | 14 | from tornado.websocket import WebSocketHandler, websocket_connect |
15 | 15 | from tornado.httpclient import HTTPRequest |
16 | 16 | from tornado.escape import url_escape, json_decode, utf8 |
|
21 | 21 |
|
22 | 22 | from .managers import GatewayClient |
23 | 23 |
|
| 24 | +# Keepalive ping interval (default: 30 seconds) |
| 25 | +GATEWAY_WS_PING_INTERVAL_SECS = int(os.getenv('GATEWAY_WS_PING_INTERVAL_SECS', 30)) |
| 26 | + |
24 | 27 |
|
25 | 28 | class WebSocketChannelsHandler(WebSocketHandler, IPythonHandler): |
26 | 29 |
|
27 | 30 | session = None |
28 | 31 | gateway = None |
29 | 32 | kernel_id = None |
| 33 | + ping_callback = None |
30 | 34 |
|
31 | 35 | def set_default_headers(self): |
32 | 36 | """Undo the set_default_headers in IPythonHandler which doesn't make sense for websockets""" |
@@ -63,8 +67,18 @@ def get(self, kernel_id, *args, **kwargs): |
63 | 67 | self.kernel_id = cast_unicode(kernel_id, 'ascii') |
64 | 68 | super(WebSocketChannelsHandler, self).get(kernel_id=kernel_id, *args, **kwargs) |
65 | 69 |
|
| 70 | + def send_ping(self): |
| 71 | + if self.ws_connection is None and self.ping_callback is not None: |
| 72 | + self.ping_callback.stop() |
| 73 | + return |
| 74 | + |
| 75 | + self.ping(b'') |
| 76 | + |
66 | 77 | def open(self, kernel_id, *args, **kwargs): |
67 | 78 | """Handle web socket connection open to notebook server and delegate to gateway web socket handler """ |
| 79 | + self.ping_callback = PeriodicCallback(self.send_ping, GATEWAY_WS_PING_INTERVAL_SECS * 1000) |
| 80 | + self.ping_callback.start() |
| 81 | + |
68 | 82 | self.gateway.on_open( |
69 | 83 | kernel_id=kernel_id, |
70 | 84 | message_callback=self.write_message, |
|
0 commit comments