flask-socketio
实施说明
访问 Flask 的上下文全局变量
SocketIO 事件的处理程序与路由的处理程序不同,这在 SocketIO 处理程序中可以做什么和不能做什么方面引入了很多混乱。主要区别在于为客户端生成的所有 SocketIO 事件都发生在单个长时间运行的请求的上下文中。
尽管存在差异,Flask-SocketIO 试图通过使环境类似于常规 HTTP 请求的环境来更轻松地使用 SocketIO 事件处理程序。以下列表描述了哪些有效,哪些无效:
- 应用程序上下文在调用事件处理程序之前被推送,
current_app
并且g
可供处理程序使用。 - 请求上下文也会在调用处理程序之前被推送,同时也是
request
可用session
的。但请注意,WebSocket 事件没有与之关联的单个请求,因此启动连接的请求上下文会为连接生命周期内调度的所有事件推送。 - 使用设置为连接的唯一会话 ID
request
的成员增强了上下文全局。sid
此值用作添加客户端的初始房间。 - 使用包含当前处理的namespace和事event 参数的命名空间和事件成员增强了请求上下文全局。事件成员是一个包含
message
和args的字典。 - 这
session
context global 的行为方式与常规请求不同。建立 SocketIO 连接时的用户会话副本可供在该连接上下文中调用的处理程序使用。如果 SocketIO 处理程序修改了会话,修改后的会话将保留给未来的 SocketIO 处理程序,但常规 HTTP 路由处理程序不会看到这些更改。实际上,当 SocketIO 处理程序修改会话时,会专门为这些处理程序创建会话的“分支”。这种限制的技术原因是为了保存用户会话,需要将 cookie 发送到客户端,并且需要 HTTP 请求和响应,这在 SocketIO 连接中不存在。当使用诸如 Flask-Session 或 Flask-KVSession 扩展提供的服务器端会话时, - 不会为 SocketIO 事件处理程序调用和
before_request
挂钩。after_request
- SocketIO 处理程序可以采用自定义装饰器,但大多数 Flask 装饰器不适合用于 SocketIO 处理程序,因为
Response
在 SocketIO 连接期间没有对象的概念。
身份验证
应用程序的一个常见需求是验证其用户的身份。基于 Web 表单和 HTTP 请求的传统机制不能用于 SocketIO 连接,因为没有地方可以发送 HTTP 请求和响应。如有必要,应用程序可以实现自定义登录表单,当用户按下提交按钮时,该表单将凭据作为 SocketIO 消息发送到服务器。
但是,在大多数情况下,在建立 SocketIO 连接之前执行传统的身份验证过程会更方便。然后,用户的身份可以记录在用户会话或 cookie 中,稍后当建立 SocketIO 连接时,SocketIO 事件处理程序将可以访问该信息。
Socket.IO 协议的最新版本包括在连接期间传递带有身份验证信息的字典的能力。这是客户端包含令牌或其他身份验证详细信息的理想位置。如果客户端使用此功能,服务器将提供此字典作为connect
事件处理程序的参数,如上所示。
使用 Flask-Login 和 Flask-SocketIO
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_required
装饰器不能与 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)