base.py 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. import logging
  2. import sys
  3. import traceback
  4. from collections import defaultdict
  5. import user_agents
  6. from ..utils import catch_exp_call
  7. logger = logging.getLogger(__name__)
  8. class Session:
  9. """
  10. 会话对象,由Backend创建
  11. 属性:
  12. info 表示会话信息的对象
  13. save 会话的数据对象,提供用户在对象上保存一些会话相关数据
  14. 由Task在当前Session上下文中调用:
  15. get_current_session
  16. get_current_task_id
  17. get_scope_name
  18. pop_scope
  19. push_scope
  20. send_task_command
  21. next_client_event
  22. on_task_exception
  23. register_callback
  24. defer_call
  25. 由Backend调用:
  26. send_client_event
  27. get_task_commands
  28. close
  29. Task和Backend都可调用:
  30. closed
  31. Session是不同的后端Backend与协程交互的桥梁:
  32. 后端Backend在接收到用户浏览器的数据后,会通过调用 ``send_client_event`` 来通知会话,进而由Session驱动协程的运行。
  33. Task内在调用输入输出函数后,会调用 ``send_task_command`` 向会话发送输入输出消息指令, Session将其保存并留给后端Backend处理。
  34. """
  35. @staticmethod
  36. def get_current_session() -> "Session":
  37. raise NotImplementedError
  38. @staticmethod
  39. def get_current_task_id():
  40. raise NotImplementedError
  41. def __init__(self, session_info):
  42. """
  43. :param session_info: 会话信息。可以通过 Session.info 访问
  44. """
  45. self.info = session_info
  46. self.save = {}
  47. self.scope_stack = defaultdict(lambda: ['ROOT']) # task_id -> scope栈
  48. self.deferred_functions = [] # 会话结束时运行的函数
  49. self._closed = False
  50. def get_scope_name(self, idx):
  51. """获取当前任务的scope栈检索scope名
  52. :param int idx: scope栈的索引
  53. :return: scope名,不存在时返回 None
  54. """
  55. task_id = type(self).get_current_task_id()
  56. try:
  57. return self.scope_stack[task_id][idx]
  58. except IndexError:
  59. raise ValueError("Scope not found")
  60. def pop_scope(self):
  61. """弹出当前scope
  62. :return: 当前scope名
  63. """
  64. task_id = type(self).get_current_task_id()
  65. try:
  66. return self.scope_stack[task_id].pop()
  67. except IndexError:
  68. raise ValueError("ROOT Scope can't pop")
  69. def push_scope(self, name):
  70. """进入新scope"""
  71. task_id = type(self).get_current_task_id()
  72. self.scope_stack[task_id].append(name)
  73. def send_task_command(self, command):
  74. raise NotImplementedError
  75. def next_client_event(self) -> dict:
  76. """获取来自客户端的下一个事件。阻塞调用,若在等待过程中,会话被用户关闭,则抛出SessionClosedException异常"""
  77. raise NotImplementedError
  78. def send_client_event(self, event):
  79. raise NotImplementedError
  80. def get_task_commands(self) -> list:
  81. raise NotImplementedError
  82. def close(self, nonblock=False):
  83. """Close current session
  84. :param bool nonblock: Don't block thread. Used in closing from backend.
  85. """
  86. if self._closed:
  87. return
  88. self._closed = True
  89. self.deferred_functions.reverse()
  90. while self.deferred_functions:
  91. func = self.deferred_functions.pop()
  92. catch_exp_call(func, logger)
  93. def closed(self) -> bool:
  94. return self._closed
  95. def on_task_exception(self):
  96. from ..output import toast
  97. from . import run_js
  98. logger.exception('Error')
  99. type, value, tb = sys.exc_info()
  100. lines = traceback.format_exception(type, value, tb)
  101. traceback_msg = ''.join(lines)
  102. traceback_msg = 'Internal Server Error\n' + traceback_msg
  103. try:
  104. toast('应用发生内部错误', duration=1, color='error')
  105. run_js("console.error(traceback_msg)", traceback_msg=traceback_msg)
  106. except Exception:
  107. pass
  108. def register_callback(self, callback, **options):
  109. """ 向Session注册一个回调函数,返回回调id
  110. Session需要保证当收到前端发送的事件消息 ``{event: "callback",task_id: 回调id, data:...}`` 时,
  111. ``callback`` 回调函数被执行, 并传入事件消息中的 ``data`` 字段值作为参数
  112. """
  113. raise NotImplementedError
  114. def defer_call(self, func):
  115. """设置会话结束时调用的函数。可以用于资源清理。
  116. 在会话中可以多次调用 `defer_call()` ,会话结束后将会顺序执行设置的函数。
  117. :param func: 话结束时调用的函数
  118. """
  119. """设置会话结束时调用的函数。可以用于资源清理。"""
  120. self.deferred_functions.append(func)
  121. def get_session_info_from_headers(headers):
  122. """从Http请求头中获取会话信息
  123. :param headers: 字典类型的Http请求头
  124. :return: 表示会话信息的对象,属性有:
  125. * ``user_agent`` : 用户浏览器信息。可用字段见 https://github.com/selwin/python-user-agents#usage
  126. * ``user_language`` : 用户操作系统使用的语言
  127. * ``server_host`` : 当前会话的服务器host,包含域名和端口,端口为80时可以被省略
  128. * ``origin`` : 当前用户的页面地址. 包含 协议、主机、端口 部分. 比如 ``'http://localhost:8080'`` .
  129. 可能为空,但保证当用户的页面地址不在当前服务器下(即 主机、端口部分和 ``server_host`` 不一致)时有值.
  130. """
  131. ua_str = headers.get('User-Agent', '')
  132. ua = user_agents.parse(ua_str)
  133. user_language = headers.get('Accept-Language', '').split(',', 1)[0].split(' ', 1)[0].split(';', 1)[0]
  134. server_host = headers.get('Host', '')
  135. origin = headers.get('Origin', '')
  136. session_info = dict(user_agent=ua, user_language=user_language,
  137. server_host=server_host, origin=origin)
  138. return session_info