一、访问 Flask 的上下文全局变量
SocketIO 事件的处理程序与路由的处理程序不同,这在 SocketIO 处理程序中可以做什么和不能做什么引起了很多混淆。主要区别在于,为客户端生成的所有 SocketIO 事件都发生在单个长时间运行的请求的上下文中。
尽管存在差异,但 Flask-SocketIO 尝试通过使环境类似于常规 HTTP 请求的环境来更轻松地使用 SocketIO 事件处理程序。以下列表描述了哪些有效,哪些无效:
-
应用程序上下文调用的事件处理决策前推
current_app
,并g
提供给处理器。 -
在调用处理程序之前也会推送请求上下文,这也是使
request
和session
可用的。但请注意,WebSocket 事件没有与它们关联的单个请求,因此启动连接的请求上下文会被推送到连接生命周期内调度的所有事件。 -
使用设置为连接的唯一会话
IDrequest
的sid
成员增强了上下文全局。该值用作添加客户端的初始房间。 -
使用 包含当前处理的命名空间和事件参数的和成员
request
增强了上下文全局。该成员是一个带有和键的字典。namespaceeventeventmessageargs
-
这
sessioncontext global
的行为方式与常规请求不同。建立 SocketIO 连接时的用户会话副本可供在该连接的上下文中调用的处理程序使用。如果 SocketIO 处理程序修改会话,则修改后的会话将保留以供将来的 SocketIO 处理程序使用,但常规 HTTP 路由处理程序不会看到这些更改。实际上,当 SocketIO 处理程序修改会话时,会专门为这些处理程序创建会话的“分叉”。这种限制的技术原因是为了保存用户会话,需要向客户端发送一个 cookie,这需要 HTTP 请求和响应,而这在 SocketIO 连接中不存在。当使用服务器端会话时,例如 Flask-Session 或 Flask-KVSession 扩展提供的会话, -
在
before_request
和after_request
钩子不调用的SocketIO事件处理程序。 -
SocketIO 处理程序可以采用自定义装饰器,但大多数 Flask 装饰器不适用于 SocketIO 处理程序,因为Response在 SocketIO 连接期间没有对象的概念。
二、认证
应用程序的一个常见需求是验证其用户的身份。传统的基于 Web 表单和 HTTP 请求的机制不能用于 SocketIO 连接,因为没有地方发送 HTTP 请求和响应。如有必要,应用程序可以实现自定义登录表单,当用户按下提交按钮时,该表单将凭据作为 SocketIO 消息发送到服务器。
但是,在大多数情况下,在建立 SocketIO 连接之前执行传统的身份验证过程会更方便。然后,用户的身份可以记录在用户会话或 cookie 中,稍后当 SocketIO 连接建立时,SocketIO 事件处理程序可以访问该信息。
Socket.IO 协议的最新修订版包括在连接期间传递带有身份验证信息的字典的能力。这是客户端包含令牌或其他身份验证详细信息的理想位置。如果客户端使用此功能,服务器将提供此字典作为connect事件处理程序的参数,如上所示。
三、在 Flask-SocketIO 中使用 Flask-Login
Flask-SocketIO 可以访问Flask-Login维护的登录信息 。在执行常规 Flask-Login 身份验证并login_user()
调用该函数以记录用户会话中的用户后,任何 SocketIO 连接都将有权访问current_user
上下文变量:
@socketio.on('connect')
def connect_handler():
if current_user.is_authenticated:
emit('my response',
{'message': '{0} has joined'.format(current_user.name)},
broadcast=True)
else:
return False # not allowed here
请注意,login_require
d装饰器不能与 SocketIO 事件处理程序一起使用,但可以按如下方式创建断开非身份验证用户的自定义装饰器:
import functools
from flask import request
from flask_login import current_user
from flask_socketio import disconnect, emit
def authenticated_only(f):
@functools.wraps(f)
def wrapped(*args, **kwargs):
if not current_user.is_authenticated:
disconnect()
else:
return f(*args, **kwargs)
return wrapped
@socketio.on('my event')
@authenticated_only
def handle_my_custom_event(data):
emit('my response', {'message': '{0} has joined'.format(current_user.name)},
broadcast=True)