You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

102 lines
3.4 KiB

4 years ago
  1. """Tornado websocket handler to serve a terminal interface.
  2. """
  3. # Copyright (c) Jupyter Development Team
  4. # Copyright (c) 2014, Ramalingam Saravanan <sarava@sarava.net>
  5. # Distributed under the terms of the Simplified BSD License.
  6. from __future__ import absolute_import, print_function
  7. # Python3-friendly imports
  8. try:
  9. from urllib.parse import urlparse
  10. except ImportError:
  11. from urlparse import urlparse
  12. import json
  13. import logging
  14. import tornado.web
  15. import tornado.websocket
  16. def _cast_unicode(s):
  17. if isinstance(s, bytes):
  18. return s.decode('utf-8')
  19. return s
  20. class TermSocket(tornado.websocket.WebSocketHandler):
  21. """Handler for a terminal websocket"""
  22. def initialize(self, term_manager):
  23. self.term_manager = term_manager
  24. self.term_name = ""
  25. self.size = (None, None)
  26. self.terminal = None
  27. self._logger = logging.getLogger(__name__)
  28. def origin_check(self, origin=None):
  29. """Deprecated: backward-compat for terminado <= 0.5."""
  30. return self.check_origin(origin or self.request.headers.get('Origin'))
  31. def open(self, url_component=None):
  32. """Websocket connection opened.
  33. Call our terminal manager to get a terminal, and connect to it as a
  34. client.
  35. """
  36. # Jupyter has a mixin to ping websockets and keep connections through
  37. # proxies alive. Call super() to allow that to set up:
  38. super(TermSocket, self).open(url_component)
  39. self._logger.info("TermSocket.open: %s", url_component)
  40. url_component = _cast_unicode(url_component)
  41. self.term_name = url_component or 'tty'
  42. self.terminal = self.term_manager.get_terminal(url_component)
  43. for s in self.terminal.read_buffer:
  44. self.on_pty_read(s)
  45. self.terminal.clients.append(self)
  46. self.send_json_message(["setup", {}])
  47. self._logger.info("TermSocket.open: Opened %s", self.term_name)
  48. def on_pty_read(self, text):
  49. """Data read from pty; send to frontend"""
  50. self.send_json_message(['stdout', text])
  51. def send_json_message(self, content):
  52. json_msg = json.dumps(content)
  53. self.write_message(json_msg)
  54. def on_message(self, message):
  55. """Handle incoming websocket message
  56. We send JSON arrays, where the first element is a string indicating
  57. what kind of message this is. Data associated with the message follows.
  58. """
  59. ##logging.info("TermSocket.on_message: %s - (%s) %s", self.term_name, type(message), len(message) if isinstance(message, bytes) else message[:250])
  60. command = json.loads(message)
  61. msg_type = command[0]
  62. if msg_type == "stdin":
  63. self.terminal.ptyproc.write(command[1])
  64. elif msg_type == "set_size":
  65. self.size = command[1:3]
  66. self.terminal.resize_to_smallest()
  67. def on_close(self):
  68. """Handle websocket closing.
  69. Disconnect from our terminal, and tell the terminal manager we're
  70. disconnecting.
  71. """
  72. self._logger.info("Websocket closed")
  73. if self.terminal:
  74. self.terminal.clients.remove(self)
  75. self.terminal.resize_to_smallest()
  76. self.term_manager.client_disconnected(self)
  77. def on_pty_died(self):
  78. """Terminal closed: tell the frontend, and close the socket.
  79. """
  80. self.send_json_message(['disconnect', 1])
  81. self.close()