使用新版本 Tornado 开发 Websocket 程序的问题
最近,在用 tornado 开发 Websocket 程序时,新装了开发环境。发现原来能够运行的程序突然出错了。tornado.websocket.WebSocketHandler 的派生类运行时,一旦有客户端连接,就报出如下错误,并拒绝连接:
TypeError: WebSocketSubscription.__init__() takes 1 positional argument but 3 were given
新装环境使用的 Tornado 版本是 6.3.2。查找官方文档(https://www.osgeo.cn/tornado/websocket.html?highlight=websocket#module-tornado.websocket),使用其提供的如下样例程序进行测试是没有问题的。
class EchoWebSocket(tornado.websocket.WebSocketHandler):
def open(self):
print("WebSocket opened")
def on_message(self, message):
self.write_message(u"You said: " + message)
def on_close(self):
print("WebSocket closed")
和我们的原有程序相比,这个程序没有派生类的初始化函数。如果在这个程序上加上初始化函数,并按原有方式调用父类初始化函数,则同样会产生上述问题。
class EchoWebSocket(tornado.websocket.WebSocketHandler):
def __init__(self):
super().__init__()
def open(self):
print("WebSocket opened")
def on_message(self, message):
self.write_message(u"You said: " + message)
def on_close(self):
print("WebSocket closed")
官方文档中,给出了初始化时应当给出的3个调用参数:
1. application: tornado.web.Application、
2. request: tornado.httputil.HTTPServerRequest、
3. **kwargs: Any
但文档中并未给出派生类调用初始化__init__() 函数的样例,使用以前版本时,也从未遇到这个问题。由于实际运行时,WebSocketHandler 的是在连接生成实例时自动调用初始化的,所以也不可能在连接时给出明确的参数。如果不调用父类的初始化,派生类的各种初始化操作就无法执行,所以必须解决这个问题。(当然,也可以将 WebSokcetHandler 对象作为成员加入原来的派生类,但这涉及到程序比较大的修改。)
在参数数量和类型不明确的情况下,Python 程序常用 *args
和 **kwargs
的方式来传递参数,试了一下,在这种情况下是奏效的,简单加了两个参数后,程序就能够正常运行。修改后的样例程序如下:
class EchoWebSocket(tornado.websocket.WebSocketHandler):
def __init__(self, *args, **kwargs):
super().__init__( *args, **kwargs)
def open(self):
print("WebSocket opened")
def on_message(self, message):
self.write_message(u"You said: " + message)
def on_close(self):
print("WebSocket closed")
和我们以前使用的版本相比,新版本还有一些值得注意特性,例如:
1. 增加了check_origin 函数检测跨源,以前是不阻止非同源接入的;
2. 可以以更简洁方式启动 WebSocketHandler 对象。
检查跨源程序的例子(无条件允许跨源):
def check_origin(self, origin):
return True
启动对象的例子:
def make_app():
return tornado.web.Application([
(r"/websocket", EchoWebSocket),
])
async def main():
app = make_app()
app.listen(8000)
await asyncio.Event().wait()
if __name__ == "__main__":
asyncio.run(main())