diff --git a/terminado/websocket.py b/terminado/websocket.py index 033da3d..f886023 100644 --- a/terminado/websocket.py +++ b/terminado/websocket.py @@ -48,7 +48,14 @@ def _cast_unicode(s): class TermSocket(tornado.websocket.WebSocketHandler): """Handler for a terminal websocket""" - def initialize(self, term_manager): + def initialize(self, term_manager, keep_alive_time=0): + """ + keep_alive_time - if there is no activity for x seconds specified in keep_alive_time, + a websocket ping message will be sent. Default is 0. Set it to 0 to + disable sending the ping message. This feature is used to keep proxies + such as nginx from automatically closing the connection when there is + no activity. + """ self.term_manager = term_manager self.term_name = "" self.size = (None, None) @@ -56,6 +63,26 @@ def initialize(self, term_manager): self._logger = logging.getLogger(__name__) + #send a ping if there is no activity for more than this many of seconds + self.keep_alive_time = keep_alive_time + self.last_activity_time = 0 + + def __timer(self): + if self.ws_connection is not None: + io_loop = self.ws_connection.stream.io_loop + t = io_loop.time() + if t - self.last_activity_time > self.keep_alive_time: + self.ping(b"ping") + #logging.debug('websocket ping') + self.last_activity_time = t + io_loop.add_timeout(max(0.2*self.keep_alive_time, 0.25), self.__timer) + + def __update_last_activity_time(self): + if self.ws_connection is not None: + io_loop = self.ws_connection.stream.io_loop + t = io_loop.time() + self.last_activity_time = t + def origin_check(self, origin=None): """Deprecated: backward-compat for terminado <= 0.5.""" return self.check_origin(origin or self.request.headers.get('Origin')) @@ -77,16 +104,25 @@ def open(self, url_component=None): self.terminal.clients.append(self) self.send_json_message(["setup", {}]) + + if self.keep_alive_time>0: + self.__update_last_activity_time() + io_loop = self.ws_connection.stream.io_loop + io_loop.add_timeout(max(0.2*self.keep_alive_time, 0.25), self.__timer) + self._logger.info("TermSocket.open: Opened %s", self.term_name) - + def on_pty_read(self, text): """Data read from pty; send to frontend""" self.send_json_message(['stdout', text]) + def send_json_message(self, content): json_msg = json.dumps(content) self.write_message(json_msg) - + if self.keep_alive_time>0: + self.__update_last_activity_time() + def on_message(self, message): """Handle incoming websocket message @@ -94,6 +130,8 @@ def on_message(self, message): what kind of message this is. Data associated with the message follows. """ ##logging.info("TermSocket.on_message: %s - (%s) %s", self.term_name, type(message), len(message) if isinstance(message, bytes) else message[:250]) + if self.keep_alive_time>0: + self.__update_last_activity_time() command = json.loads(message) msg_type = command[0]