Skip to content

Commit

Permalink
feat: add session.get_info() to get session info
Browse files Browse the repository at this point in the history
  • Loading branch information
wang0618 committed May 3, 2020
1 parent 1510b6d commit fe6b0b0
Show file tree
Hide file tree
Showing 11 changed files with 138 additions and 13 deletions.
11 changes: 8 additions & 3 deletions pywebio/platform/aiohttp.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@
import fnmatch
import logging
from functools import partial
from urllib.parse import urlparse
from os import path, listdir
from urllib.parse import urlparse

from aiohttp import web

from .tornado import open_webbrowser_on_server_started
from ..session import CoroutineBasedSession, ThreadBasedSession, register_session_implement_for_target, AbstractSession
from ..session.base import get_session_info_from_headers
from ..utils import get_free_port, STATIC_PATH

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -63,11 +65,14 @@ def close_from_session():
ioloop.create_task(ws.close())
logger.debug("WebSocket closed from session")

session_info = get_session_info_from_headers(request.headers)
if session_cls is CoroutineBasedSession:
session = CoroutineBasedSession(target, on_task_command=send_msg_to_client,
session = CoroutineBasedSession(target, session_info=session_info,
on_task_command=send_msg_to_client,
on_session_close=close_from_session)
elif session_cls is ThreadBasedSession:
session = ThreadBasedSession(target, on_task_command=send_msg_to_client,
session = ThreadBasedSession(target, session_info=session_info,
on_task_command=send_msg_to_client,
on_session_close=close_from_session, loop=ioloop)
else:
raise RuntimeError("Don't support session type:%s" % session_cls)
Expand Down
4 changes: 3 additions & 1 deletion pywebio/platform/httpbased.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import time

from ..session import CoroutineBasedSession, AbstractSession, register_session_implement_for_target
from ..session.base import get_session_info_from_headers
from ..utils import random_str, LRUDict


Expand Down Expand Up @@ -146,7 +147,8 @@ def handle_request(self, context: HttpContext):

webio_session_id = random_str(24)
context.set_header('webio-session-id', webio_session_id)
webio_session = self.session_cls(self.target)
session_info = get_session_info_from_headers(context.request_headers())
webio_session = self.session_cls(self.target, session_info=session_info)
cls._webio_sessions[webio_session_id] = webio_session
elif request_headers['webio-session-id'] not in cls._webio_sessions: # WebIOSession deleted
context.set_content([dict(command='close_session')], json_type=True)
Expand Down
8 changes: 6 additions & 2 deletions pywebio/platform/tornado.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

from ..session import CoroutineBasedSession, ThreadBasedSession, ScriptModeSession, \
register_session_implement_for_target, AbstractSession
from ..session.base import get_session_info_from_headers
from ..utils import get_free_port, wait_host_port, STATIC_PATH

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -78,11 +79,14 @@ def open(self):

self._close_from_session_tag = False # 由session主动关闭连接

session_info = get_session_info_from_headers(self.request.headers)
if session_cls is CoroutineBasedSession:
self.session = CoroutineBasedSession(target, on_task_command=self.send_msg_to_client,
self.session = CoroutineBasedSession(target, session_info=session_info,
on_task_command=self.send_msg_to_client,
on_session_close=self.close_from_session)
elif session_cls is ThreadBasedSession:
self.session = ThreadBasedSession(target, on_task_command=self.send_msg_to_client,
self.session = ThreadBasedSession(target, session_info=session_info,
on_task_command=self.send_msg_to_client,
on_session_close=self.close_from_session,
loop=asyncio.get_event_loop())
else:
Expand Down
37 changes: 36 additions & 1 deletion pywebio/session/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
r"""
.. autofunction:: get_info
.. autofunction:: run_async
.. autofunction:: run_asyncio_coroutine
.. autofunction:: register_thread
Expand All @@ -21,7 +23,7 @@
# 当前进程中正在使用的会话实现的列表
_active_session_cls = []

__all__ = ['run_async', 'run_asyncio_coroutine', 'register_thread', 'hold', 'defer_call']
__all__ = ['run_async', 'run_asyncio_coroutine', 'register_thread', 'hold', 'defer_call', 'get_info']


def register_session_implement_for_target(target_func):
Expand Down Expand Up @@ -175,3 +177,36 @@ def cleanup():
"""
get_current_session().defer_call(func)
return func


def get_info():
""" 获取当前会话的相关信息
:return: 表示会话信息的对象,属性有:
* ``user_agent`` : 表示用户浏览器信息的对象,属性有
* ``is_mobile`` (bool): 用户使用的设备是否为手机 (比如 iPhone, Android phones, Blackberry, Windows Phone 等设备)
* ``is_tablet`` (bool): 用户使用的设备是否为平板 (比如 iPad, Kindle Fire, Nexus 7 等设备)
* ``is_pc`` (bool): 用户使用的设备是否为桌面电脑 (比如运行 Windows, OS X, Linux 的设备)
* ``is_touch_capable`` (bool): 用户使用的设备是否支持触控
* ``browser.family`` (str): 浏览器家族. 比如 'Mobile Safari'
* ``browser.version`` (tuple): 浏览器版本元组. 比如 (5, 1)
* ``browser.version_string`` (str): 浏览器版本字符串. 比如 '5.1'
* ``os.family`` (str): 操作系统家族. 比如 'iOS'
* ``os.version`` (tuple): 操作系统版本元组. 比如 (5, 1)
* ``os.version_string`` (str): 操作系统版本字符串. 比如 '5.1'
* ``device.family`` (str): 设备家族. 比如 'iPhone'
* ``device.brand`` (str): 设备品牌. 比如 'Apple'
* ``device.model`` (str): 设备幸好. 比如 'iPhone'
* ``user_language`` (str): 用户操作系统使用的语言. 比如 ``'zh-CN'``
* ``server_host`` (str): 当前会话的服务器host,包含域名和端口,端口为80时可以被省略
* ``origin`` : 当前用户的页面地址. 包含 协议、主机、端口 部分. 比如 ``'http://localhost:8080'`` .
只在当用户的页面地址不在当前服务器下(即 主机、端口部分和 ``server_host`` 不一致)时有值.
返回值的 ``user_agent`` 属性是通过user_agents库进行解析生成的。参见 https://github.com/selwin/python-user-agents#usage
"""
return get_current_session().info
35 changes: 33 additions & 2 deletions pywebio/session/base.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
import user_agents
from ..utils import ObjectDict


class AbstractSession:
"""
会话对象,由Backend创建
属性:
info 表示会话信息的对象
由Task在当前Session上下文中调用:
get_current_session
get_current_task_id
Expand All @@ -27,6 +34,7 @@ class AbstractSession:
后端Backend在接收到用户浏览器的数据后,会通过调用 ``send_client_event`` 来通知会话,进而由Session驱动协程的运行。
Task内在调用输入输出函数后,会调用 ``send_task_command`` 向会话发送输入输出消息指令, Session将其保存并留给后端Backend处理。
"""
info = object()

@staticmethod
def active_session_count() -> int:
Expand All @@ -40,9 +48,10 @@ def get_current_session() -> "AbstractSession":
def get_current_task_id():
raise NotImplementedError

def __init__(self, target, on_task_command=None, on_session_close=None, **kwargs):
def __init__(self, target, session_info, on_task_command=None, on_session_close=None, **kwargs):
"""
:param target:
:param session_info: 会话信息。可以通过 Session.info 访问
:param on_task_command: Backend向ession注册的处理函数,当 Session 收到task发送的command时调用
:param on_session_close: Backend向Session注册的处理函数,当 Session task 执行结束时调用 *
:param kwargs:
Expand Down Expand Up @@ -88,4 +97,26 @@ def defer_call(self, func):
:param func: 话结束时调用的函数
"""
raise NotImplementedError
raise NotImplementedError


def get_session_info_from_headers(headers):
"""从Http请求头中获取会话信息
:param headers: 字典类型的Http请求头
:return: 表示会话信息的对象,属性有:
* ``user_agent`` : 用户浏览器信息。可用字段见 https://github.com/selwin/python-user-agents#usage
* ``user_language`` : 用户操作系统使用的语言
* ``server_host`` : 当前会话的服务器host,包含域名和端口,端口为80时可以被省略
* ``origin`` : 当前用户的页面地址. 包含 协议、主机、端口 部分. 比如 ``'http://localhost:8080'`` .
只在当用户的页面地址不在当前服务器下(即 主机、端口部分和 ``server_host`` 不一致)时有值.
"""
ua_str = headers.get('User-Agent', '')
ua = user_agents.parse(ua_str)
user_language = headers.get('Accept-Language', '').split(',', 1)[0].split(' ', 1)[0].split(';', 1)[0]
server_host = headers.get('Host', '')
origin = headers.get('Origin', '')
session_info = ObjectDict(user_agent=ua, user_language=user_language,
server_host=server_host, origin=origin)
return session_info
3 changes: 2 additions & 1 deletion pywebio/session/coroutinebased.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ def get_current_task_id():
raise RuntimeError("No current task found in context!")
return _context.current_task_id

def __init__(self, target, on_task_command=None, on_session_close=None):
def __init__(self, target, session_info, on_task_command=None, on_session_close=None):
"""
:param target: 协程函数
:param on_task_command: 由协程内发给session的消息的处理函数
Expand All @@ -77,6 +77,7 @@ def __init__(self, target, on_task_command=None, on_session_close=None):

CoroutineBasedSession._active_session_cnt += 1

self.info = session_info
self._on_task_command = on_task_command or (lambda _: None)
self._on_session_close = on_session_close or (lambda: None)

Expand Down
11 changes: 8 additions & 3 deletions pywebio/session/threadbased.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@

from .base import AbstractSession
from ..exceptions import SessionNotFoundException, SessionClosedException, SessionException
from ..utils import random_str, LimitedSizeQueue, isgeneratorfunction, iscoroutinefunction, catch_exp_call, get_function_name
from ..utils import random_str, LimitedSizeQueue, isgeneratorfunction, iscoroutinefunction, catch_exp_call, \
get_function_name

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -54,7 +55,7 @@ def _get_task_id(thread: threading.Thread):
tname = getattr(tname, '__name__', tname)
return '%s-%s' % (tname, id(thread))

def __init__(self, target, on_task_command=None, on_session_close=None, loop=None):
def __init__(self, target, session_info, on_task_command=None, on_session_close=None, loop=None):
"""
:param target: 会话运行的函数
:param on_task_command: 当Task内发送Command给session的时候触发的处理函数
Expand All @@ -68,6 +69,7 @@ def __init__(self, target, on_task_command=None, on_session_close=None, loop=Non

ThreadBasedSession._active_session_cnt += 1

self.info = session_info
self._on_task_command = on_task_command or (lambda _: None)
self._on_session_close = on_session_close or (lambda: None)
self._loop = loop
Expand Down Expand Up @@ -316,8 +318,10 @@ def get_current_task_id(cls):

instance = None

def __init__(self, thread, on_task_command=None, loop=None):
def __init__(self, thread, session_info, on_task_command=None, loop=None):
"""
:param thread: 第一次调用PyWebIO交互函数的线程 todo 貌似本参数并不必要
:param on_task_command: 会话结束的处理函数。后端Backend在相应on_session_close时关闭连接时,
需要保证会话内的所有消息都传送到了客户端
:param loop: 事件循环。若 on_task_command 或者on_session_close中有调用使用asyncio事件循环的调用,
Expand All @@ -329,6 +333,7 @@ def __init__(self, thread, on_task_command=None, loop=None):

ThreadBasedSession._active_session_cnt += 1

self.info = session_info
self._on_task_command = on_task_command or (lambda _: None)
self._on_session_close = lambda: None
self._loop = loop
Expand Down
14 changes: 14 additions & 0 deletions pywebio/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,20 @@
STATIC_PATH = '%s/html' % project_dir


class ObjectDict(dict):
"""
Object like dict, every dict[key] can visite by dict.key
If dict[key] is `Get`, calculate it's value.
"""

def __getattr__(self, name):
ret = self.__getitem__(name)
if hasattr(ret, '__get__'):
return ret.__get__(self, ObjectDict)
return ret


def catch_exp_call(func, logger):
"""运行函数,将捕获异常记录到日志
Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
tornado>=4.3.0
python-user-agents

# extra support
flask
Expand Down
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@
],
install_requires=[
'tornado>=4.3.0', # After this version, the new async/await keywords in Python 3.5 are supported
'python-user-agents',
],
extras_require=extras_require,
project_urls={
Expand Down
26 changes: 26 additions & 0 deletions test/template.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,32 @@ def edit_row(choice, row):
put_text('to remove', anchor='to_remove')
remove('to_remove')

session_info = get_info()
put_markdown(rf"""### 会话信息
```
* `user_agent`:
* `is_mobile` (bool): {session_info.user_agent.is_mobile}
* `is_tablet` (bool): {session_info.user_agent.is_tablet}
* `is_pc` (bool): {session_info.user_agent.is_pc}
* `is_touch_capable` (bool): {session_info.user_agent.is_touch_capable}
* `browser.family` (str): {session_info.user_agent.browser.family}
* `browser.version` (tuple): {session_info.user_agent.browser.version}
* `browser.version_string` (str): {session_info.user_agent.browser.version_string}
* `os.family` (str): {session_info.user_agent.os.family}
* `os.version` (tuple): {session_info.user_agent.os.version}
* `os.version_string` (str): {session_info.user_agent.os.version_string}
* `device.family` (str): {session_info.user_agent.device.family}
* `device.brand` (str): {session_info.user_agent.device.brand}
* `device.model` (str): {session_info.user_agent.device.model}
* `user_language` (str): {session_info.user_language}
* `server_host` (str): {session_info.server_host}
* `origin` (str): {session_info.origin}
```
""", strip_indent=4)


def background_output():
put_text("Background output", anchor='background')
Expand Down

0 comments on commit fe6b0b0

Please sign in to comment.