学习笔记

flask-socketio的蓝图以及前后端分离的实施方法

近期在使用flask-socketio进行web开发的时候遇到了一个神奇的问题,就是当蓝图和flask-socketio一起使用时,放在蓝图内的socketio装饰器并不能正确监听websocket事件。因此启动了尘封已久的博客,想在这篇文章中讨论一下Flask-socketio和蓝图配合的正确方式。

当使用flask-socketio时,flask-socketio以及前端的socketio会自动选择交互模式,如果服务端不支持websocket时则不会发出HTTP 101切换到WS协议,则会以长轮询的方式进行代替。但现在前后端分离的开发方式下,使用websocket需要考虑一个跨域访问的问题,因此需要引入flask_cors跨域访问库来解决这个问题

上述代码仅保留了关键部分,省略了其他无关的部分,需要开启跨域访问保证socketio可以正常发起XHR请求,这样才会有后面的HTTP 101协议升级。这样就可以实现对websocket部分的前后端分离。

对于flask-socketio使用蓝图的部分,相较于前后段分离来说比较困难。其中一个比较容易出现的问题就是引用问题。首先贴一个flask-socketio官方的chatroom实现:Flask-SocketIO-Chat

这个官方例子中使用到了工厂模式来生产Flask app,大致的文件结构如下:

官方示例的包结构

app目录下放工厂函数,main下放真正的蓝图。引用顺序为:

这里面有几个需要解析的点:
1、使用工厂模式
2、在工厂函数中引用蓝图
3、在main的__init__.py蓝图声明后引入其他逻辑代码
4、包结构

工厂模式 & 包结构
我实验过没有工厂模式的版本,假设app的创建直接在chat.py中完成,那引用顺序就变为

在这样的引用结构中会存在两个问题。
循环引用重复引用
首先说循环引用,这个对应的就是前面说的第三点(原理相通),如果在chat.py的蓝图声明在chat.py import可以直接执行的位置,则会引发循环引用。引用链如下

然后再说一下第二种情况重复引用。这种情况会比较复杂,目前看只要不按照官方示例来写基本就会出现这个问题,这也就同时前面说的2、4两个点。相比起前面出现的循环引用Python会直接抛出一个循环引用异常,而防止出现循环引用的方法是使用一个函数将会产生循环引用部分的引用代码,变成函数调用,然后利用函数不会在import期间被调用(只要你不写调用语句在import的执行区域)的特性来做一个延迟加载,具体示例如下

这个因为重复引用带来的问题就很麻烦了

我曾经使用过的一种包结构

相较于官方包结构,这里面我将工厂模式的生成器放在了socketio_learn.py中,因为生成器和socketio变量在一起,所以我需要在bt.py这个蓝图中引入socketio_learn.py这个文件,但是socketio在这次引用中又被重新定义了一遍,后socketio会继续引用bt.py(通过blueprint/__init__.py引用)这个蓝图,但是因为该文件已经存在,且没有明显的循环引用代码,所以会跳过对该变量的引用,继续向下执行,将websocket监听逻辑注入到后面因为引入而被创建的socketio变量中,又因为该文件是被引入,无法触发__main__魔术方法,所以该socketio无法被run执行。引用结束后因引用(默认使用了前面说的避免循环引用的方法)在函数内为局部变量,而websocket注册在局部变量上,函数退出后被销毁。只留下了一个空的全局socketio。因此没办法对事件进行绑定

而官方示例充分使用了__init__.py来规避了这个问题。

最后需要补充一个问题,就是在使用flask-socketio的emit事件时会出现一种情况:很多websocket消息并不会实时传递,而是在函数运行(或者程序运行)结束以后才会返回给前端。针对这个问题需要使用

来解决。解决方法来自StackOverflow

你可能也会喜欢...

发表评论

邮箱地址不会被公开。 必填项已用*标注