阅读(4646) (1)

Tornado 与浏览器的双向通信

2022-03-09 10:13:08 更新

WebSocket 协议的实现,WebSockets 允许浏览器和服务器之间的双向通信。

所有主流浏览器的当前版本都支持 WebSockets,尽管不支持 WebSockets 的旧版本仍在使用

该模块实现了 RFC 6455 中定义的 WebSocket 协议的最终版本。某些浏览器版本(尤其是 Safari 5.x)实现了该协议的早期草案(称为“draft 76”)并且与该模块不兼容。

在 4.0 版更改: 删除了对草案 76 协议版本的支持。

class tornado.websocket.WebSocketHandler(application: tornado.web.Application, request: tornado.httputil.HTTPServerRequest, **kwargs)

子类化此类以创建基本的 WebSocket 处理程序。

重写 ​on_message来处理传入的消息,并使用 ​write_message将消息发送到客户端。 您还可以覆盖 ​open和 ​on_close来处理打开和关闭的连接。

可以通过覆盖 ​set_default_headers或 ​prepare来发送自定义升级响应表头。

这是一个示例 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")

WebSockets 不是标准的 HTTP 连接。WebSocket只是使用HTTP协议来完成一部分握手,但在握手之后,协议是基于消息的。 因此,大多数 Tornado HTTP 工具在这种类型的处理程序中不可用。 您可用的唯一通信方法是 ​write_message()​、​ping()​ 和 ​close()​。 同样,您的请求处理程序类应该实现 ​open()​ 方法而不是 ​get()​ 或 ​post()​。

如果您将上面的处理程序映射到应用程序中的 ​/websocket​,您可以在 JavaScript 中调用它:

var ws = new WebSocket("ws://localhost:8888/websocket");
ws.onopen = function() {
   ws.send("Hello, world");
};
ws.onmessage = function (evt) {
   alert(evt.data);
};

这个脚本会弹出一个警告框,上面写着“You said:hello,world”。

Web 浏览器允许任何站点打开与任何其他站点的 websocket 连接,而不是使用同源策略来管理来自 JavaScript 的其他网络访问。 这可能令人惊讶并且是一个潜在的安全漏洞,因此由于 Tornado 4.0 ​WebSocketHandler要求希望接收跨域 websocket 的应用程序通过覆盖 ​check_origin方法来选择加入。 不这样做是建立 websocket 连接时最可能导致 403 错误的原因。

当使用带有自签名证书的安全 websocket 连接 (​wss://​) 时,来自浏览器的连接可能会失败,因为它想要显示“接受此证书”对话框但无处显示。 在 websocket 连接成功之前,您必须首先使用相同的证书访问常规 HTML 页面以接受它。

如果应用程序设置 ​websocket_ping_interval的值为非零值,则会定期发送 ping,如果在 ​websocket_ping_timeout之前没有收到响应,则连接将关闭。

大于 ​websocket_max_message_size应用程序设置(默认 10MiB)的消息将不被接受。

在 4.5 版更改:添加了 ​websocket_ping_interval、​websocket_ping_timeout和 ​websocket_max_message_size

事件处理程序

WebSocketHandler.open(*args, **kwargs) → Optional[Awaitable[None]]

打开新的 WebSocket 时调用。

open的参数是从​tornado.web.URLSpec正则表达式中提取的,就像tornado.web.RequestHandler.get的参数一样。

open可能是一个协程。 在 ​open返回之前不会调用 ​on_message​。

在 5.1 版更改: ​open可能是一个协程。

WebSocketHandler.on_message(message: Union[str, bytes]) → Optional[Awaitable[None]]

处理 WebSocket 上的传入消息

必须重写此方法。

在 4.5 版更改: ​on_message​可以是协程。

WebSocketHandler.on_close() → None

当 WebSocket 关闭时调用。

如果连接完全关闭并且提供了状态代码或原因短语,则这些值将作为属性用于 ​self.close_code​ 和 ​self.close_reason

在 4.0 版更改: 添加了 ​close_code和 ​close_reason属性。

WebSocketHandler.select_subprotocol(subprotocols: List[str]) → Optional[str]

重写以实现子协议协商。

subprotocols是标识客户端提出的子协议的字符串列表。 可以重写此方法以返回其中一个字符串以选择它,或者返回 ​None以不选择子协议。

未能选择子协议不会自动中止连接,尽管如果没有选择任何建议的子协议,客户端可能会关闭连接。

列表可能为空,在这种情况下,此方法必须返回 ​None​。 即使没有提出子协议,该方法也总是只调用一次,以便可以将这一事实告知处理程序。

在 5.1 版更改: 以前,如果客户端没有提出子协议,则使用包含空字符串而不是空列表的列表调用此方法。

WebSocketHandler.selected_subprotocol

select_subprotocol返回的子协议。

5.1 版中的新功能。

WebSocketHandler.on_ping(data: bytes) → None

在收到 ping 帧时调用。

输出

WebSocketHandler.write_message(message: Union[bytes, str, Dict[str, Any]], binary: bool = False) → Future[None]

将给定消息发送到此 Web Socket 的客户端。

消息可以是字符串或字典(将被编码为 json)。 如果 ​binary参数为 false,则消息将作为 utf8 发送; 在二进制模式下,任何字节串都是允许的。

如果连接已经关闭,则引发 ​WebSocketClosedError​。 返回可用于流量控制的 ​Future​。

在 3.2 版更改:添加了 ​WebSocketClosedError​(以前关闭的连接会引发 ​AttributeError​)

在 4.3 版更改: 返回可用于流量控制的 ​Future​。

在 5.0 版更改: 持续引发 ​WebSocketClosedError​。 以前有时会引发 ​StreamClosedError​。

WebSocketHandler.close(code: Optional[int] = None, reason: Optional[str] = None) → None

关闭此 Web Socket。

一旦关闭握手成功,套接字将被关闭。

code可以是数字状态代码,取自 RFC 6455 第 7.4.1 节中定义的值。​reason​可能是关于连接关闭原因的文本消息。 这些值可供客户端使用,但不会被 websocket 协议解释。

在 4.0 版更改: 添加了​code​和​reason​参数。

配置

WebSocketHandler.check_origin(origin: str) → bool

覆盖以启用对允许备用来源的支持。

origin参数是 ​OriginHTTP 表头的值,即负责发起此请求的 url。 不发送此表头的客户端不会调用此方法; 这样的请求总是被允许的(因为所有实现 WebSockets 的浏览器都支持这个表头,并且非浏览器客户端没有相同的跨站点安全问题)。

应该返回 ​True来接受请求或 ​False来拒绝它。 默认情况下,拒绝所有来自主机以外的主机的请求。

这是针对浏览器上的跨站点脚本攻击的安全保护,因为允许 WebSocket 绕过通常的同源策略并且不使用 CORS 表头。

注意:这是一项重要的安全措施;不要在不了解安全隐患的情况下禁用它。 特别是,如果您的身份验证是基于 cookie 的,您要么限制 ​check_origin() 允许的来源,要么为 websocket 连接实现类似 XSRF 的保护。

要接受所有跨域流量(这是 Tornado 4.0 之前的默认设置),只需重写此方法以始终返回 ​True​:

def check_origin(self, origin):
    return True

要允许来自您网站的任何子域的连接,您可以执行以下操作:

def check_origin(self, origin):
    parsed_origin = urllib.parse.urlparse(origin)
    return parsed_origin.netloc.endswith(".mydomain.com")

WebSocketHandler.get_compression_options() → Optional[Dict[str, Any]]

覆盖以返回连接的压缩选项。

如果此方法返回 None(默认值),压缩将被禁用。 如果它返回一个 dict(甚至是一个空的),它将被启用。 dict 的内容可用于控制以下压缩选项:

compression_level指定压缩级别。

mem_level指定用于内部压缩状态的内存量。

WebSocketHandler.set_nodelay(value: bool) → None

为此流设置无延迟标志。

默认情况下,小消息可能会被延迟组合以最小化发送的数据包数量。 由于 Nagle 的算法和 TCP 延迟 ACK 之间的交互,这有时会导致 200-500 毫秒的延迟。 要减少此延迟(以可能增加带宽使用为代价),请在建立 websocket 连接后调用 ​self.set_nodelay(True)​。

其他

WebSocketHandler.ping(data: Union[str, bytes] = b'') → None

向远端发送 ping 帧。

data 参数允许作为 ping 消息的一部分发送少量数据(最多 125 个字节)。 请注意,并非所有 websocket 实现都会将此数据公开给应用程序。

考虑使用 ​websocket_ping_interval应用程序设置,而不是手动发送 ping。

在 5.1 版更改: data 参数现在是可选的。

WebSocketHandler.on_pong(data: bytes) → None

在收到对 ping 帧的响应时调用。

exception tornado.websocket.WebSocketClosedError

由关闭连接上的操作引发。

客户端支持

tornado.websocket.websocket_connect(url: Union[str, tornado.httpclient.HTTPRequest], callback: Optional[Callable[[Future[WebSocketClientConnection]], None]] = None, connect_timeout: Optional[float] = None, on_message_callback: Optional[Callable[[Union[None, str, bytes]], None]] = None, compression_options: Optional[Dict[str, Any]] = None, ping_interval: Optional[float] = None, ping_timeout: Optional[float] = None, max_message_size: int = 10485760, subprotocols: Optional[List[str]] = None) → Awaitable[WebSocketClientConnection]

接受一个 url 并返回一个 Future,其结果是一个 ​WebSocketClientConnection​。
compression_options ​的执行方式与 ​WebSocketHandler.get_compression_options​ 的返回值相同。
该连接支持两种操作方式。 在协程风格中,应用程序通常会在循环中调用 ​read_message​:

conn = yield websocket_connect(url)
while True:
    msg = yield conn.read_message()
    if msg is None: break
    # Do something with msg

在回调样式中,将 ​on_message_callback ​传递给 ​websocket_connect​。 在这两种样式中,​None ​消息表示连接已关闭。

subprotocols可以是指定提议的子协议的字符串列表。 当连接完成时,可以在连接对象的 ​selected_subprotocol属性上找到所选协议。

在 3.2 版更改: 也接受 ​HTTPRequest对象代替 url。

在 4.1 版更改:添加了 ​compression_options和 ​on_message_callback

在 4.5 版更改:添加了 ​ping_interval、​ping_timeout和 ​max_message_size参数,它们与 ​WebSocketHandler中的含义相同。

在 5.0 版中更改: ​io_loop参数(自 4.1 版以来已弃用)已被删除。

在 5.1 版更改: 添加了 ​subprotocols参数。

class tornado.websocket.WebSocketClientConnection(request: tornado.httpclient.HTTPRequest, on_message_callback: Optional[Callable[[Union[None, str, bytes]], None]] = None, compression_options: Optional[Dict[str, Any]] = None, ping_interval: Optional[float] = None, ping_timeout: Optional[float] = None, max_message_size: int = 10485760, subprotocols: Optional[List[str]] = [])

WebSocket 客户端连接。

这个类不应该直接实例化; 请改用 ​websocket_connect ​函数。

close(code: Optional[int] = None, reason: Optional[str] = None) → None

关闭 websocket 连接。

code​和​reason​记录在 ​WebSocketHandler.close​ 下。

write_message(message: Union[str, bytes], binary: bool = False) → Future[None]

向 WebSocket 服务器发送消息。

如果流已关闭,则引发 ​WebSocketClosedError​。 返回可用于流量控制的 ​Future

在 5.0 版更改:在关闭的流上引发的异常从 ​StreamClosedError​更改为 ​WebSocketClosedError​。

read_message(callback: Optional[Callable[[Future[Union[None, str, bytes]]], None]] = None) → Awaitable[Union[None, str, bytes]]

从 WebSocket 服务器读取消息。
如果在 WebSocket 初始化时指定了 ​on_message_callback​,则此函数将永远不会返回消息
返回结果为​future​的消息,如果连接已关闭,则返回 ​None​。 如果给定了回调参数,它将在准备好时与​future​一起调用。

ping(data: bytes = b'') → None

向远端发送 ping 帧。

data参数允许作为 ping 消息的一部分发送少量数据(最多 125 个字节)。 请注意,并非所有 websocket 实现都会将此数据公开给应用程序。

考虑对 ​websocket_connect使用 ​ping_interval参数,而不是手动发送 ping。

selected_subprotocol

服务器选择的子协议