Tornado 与浏览器的双向通信
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
参数是 Origin
HTTP 表头的值,即负责发起此请求的 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
服务器选择的子协议