tornado.py 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119
  1. import asyncio
  2. import json
  3. import logging
  4. import webbrowser
  5. import tornado
  6. import tornado.httpserver
  7. import tornado.ioloop
  8. import tornado.websocket
  9. from tornado.web import StaticFileHandler
  10. from . import STATIC_PATH
  11. from ..session import AsyncBasedSession, ThreadBasedWebIOSession, get_session_implement
  12. from ..utils import get_free_port, wait_host_port
  13. logger = logging.getLogger(__name__)
  14. def webio_handler(task_func):
  15. class WSHandler(tornado.websocket.WebSocketHandler):
  16. def check_origin(self, origin):
  17. return True
  18. def get_compression_options(self):
  19. # Non-None enables compression with default options.
  20. return {}
  21. def send_msg_to_client(self, controller: AsyncBasedSession):
  22. for msg in controller.get_task_messages():
  23. self.write_message(json.dumps(msg))
  24. def open(self):
  25. logger.debug("WebSocket opened")
  26. self.set_nodelay(True)
  27. self._close_from_session = False # 是否从session中关闭连接
  28. if get_session_implement() is AsyncBasedSession:
  29. self.controller = AsyncBasedSession(task_func, on_task_message=self.send_msg_to_client,
  30. on_session_close=self.close)
  31. else:
  32. self.controller = ThreadBasedWebIOSession(task_func, on_task_message=self.send_msg_to_client,
  33. on_session_close=self.close_from_session,
  34. loop=asyncio.get_event_loop())
  35. def on_message(self, message):
  36. data = json.loads(message)
  37. self.controller.send_client_event(data)
  38. def close_from_session(self):
  39. self._close_from_session = True
  40. self.close()
  41. def on_close(self):
  42. if not self._close_from_session:
  43. self.controller.close(no_session_close_callback=True)
  44. logger.debug("WebSocket closed")
  45. return WSHandler
  46. async def open_webbrowser_on_server_started(host, port):
  47. url = 'http://%s:%s' % (host, port)
  48. is_open = await wait_host_port(host, port, duration=5, delay=0.5)
  49. if is_open:
  50. logger.info('Openning %s' % url)
  51. webbrowser.open(url)
  52. else:
  53. logger.error('Open %s failed.' % url)
  54. def start_server(target, port=0, host='', debug=True,
  55. auto_open_webbrowser=False,
  56. websocket_max_message_size=None,
  57. websocket_ping_interval=None,
  58. websocket_ping_timeout=None,
  59. **tornado_app_settings):
  60. """Start a Tornado server to serve `target` function
  61. :param target: task function. It's a coroutine function is use AsyncBasedSession or
  62. a simple function is use ThreadBasedWebIOSession.
  63. :param port: server bind port. set ``0`` to find a free port number to use
  64. :param host: server bind host. ``host`` may be either an IP address or hostname. If it's a hostname,
  65. the server will listen on all IP addresses associated with the name.
  66. set empty string or to listen on all available interfaces.
  67. :param bool debug: Tornado debug mode
  68. :param bool auto_open_webbrowser: auto open web browser when server started
  69. :param int websocket_max_message_size: Max bytes of a message which Tornado can accept.
  70. Messages larger than the ``websocket_max_message_size`` (default 10MiB) will not be accepted.
  71. :param int websocket_ping_interval: If set to a number, all websockets will be pinged every n seconds.
  72. This can help keep the connection alive through certain proxy servers which close idle connections,
  73. and it can detect if the websocket has failed without being properly closed.
  74. :param int websocket_ping_timeout: If the ping interval is set, and the server doesn’t receive a ‘pong’
  75. in this many seconds, it will close the websocket. The default is three times the ping interval,
  76. with a minimum of 30 seconds. Ignored if ``websocket_ping_interval`` is not set.
  77. :param tornado_app_settings: Additional keyword arguments passed to the constructor of ``tornado.web.Application``.
  78. ref: https://www.tornadoweb.org/en/stable/web.html#tornado.web.Application.settings
  79. :return:
  80. """
  81. kwargs = locals()
  82. app_options = ['debug', 'websocket_max_message_size', 'websocket_ping_interval', 'websocket_ping_timeout']
  83. for opt in app_options:
  84. if kwargs[opt] is not None:
  85. tornado_app_settings[opt] = kwargs[opt]
  86. if port == 0:
  87. port = get_free_port()
  88. print('Listen on %s:%s' % (host or '0.0.0.0', port))
  89. handlers = [(r"/io", webio_handler(target)),
  90. (r"/(.*)", StaticFileHandler, {"path": STATIC_PATH, 'default_filename': 'index.html'})]
  91. app = tornado.web.Application(handlers=handlers, **tornado_app_settings)
  92. app.listen(port, address=host)
  93. if auto_open_webbrowser:
  94. tornado.ioloop.IOLoop.current().spawn_callback(open_webbrowser_on_server_started, host or '0.0.0.0', port)
  95. tornado.ioloop.IOLoop.current().start()