Tornado 與瀏覽器的雙向通信

2022-03-09 11:10 更新

WebSocket 協(xié)議的實(shí)現(xiàn),WebSockets 允許瀏覽器和服務(wù)器之間的雙向通信。

所有主流瀏覽器的當(dāng)前版本都支持 WebSockets,盡管不支持 WebSockets 的舊版本仍在使用

該模塊實(shí)現(xiàn)了 RFC 6455 中定義的 WebSocket 協(xié)議的最終版本。某些瀏覽器版本(尤其是 Safari 5.x)實(shí)現(xiàn)了該協(xié)議的早期草案(稱(chēng)為“draft 76”)并且與該模塊不兼容。

在 4.0 版更改: 刪除了對(duì)草案 76 協(xié)議版本的支持。

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

子類(lèi)化此類(lèi)以創(chuàng)建基本的 WebSocket 處理程序。

重寫(xiě) ?on_message來(lái)處理傳入的消息,并使用 ?write_message將消息發(fā)送到客戶(hù)端。 您還可以覆蓋 ?open和 ?on_close來(lái)處理打開(kāi)和關(guān)閉的連接。

可以通過(guò)覆蓋 ?set_default_headers或 ?prepare來(lái)發(fā)送自定義升級(jí)響應(yīng)表頭。

這是一個(gè)示例 WebSocket 處理程序,它將所有接收到的消息返回給客戶(hù)端:

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 不是標(biāo)準(zhǔn)的 HTTP 連接。WebSocket只是使用HTTP協(xié)議來(lái)完成一部分握手,但在握手之后,協(xié)議是基于消息的。 因此,大多數(shù) Tornado HTTP 工具在這種類(lèi)型的處理程序中不可用。 您可用的唯一通信方法是 ?write_message()?、?ping()? 和 ?close()?。 同樣,您的請(qǐng)求處理程序類(lèi)應(yīng)該實(shí)現(xiàn) ?open()? 方法而不是 ?get()? 或 ?post()?。

如果您將上面的處理程序映射到應(yīng)用程序中的 ?/websocket?,您可以在 JavaScript 中調(diào)用它:

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

這個(gè)腳本會(huì)彈出一個(gè)警告框,上面寫(xiě)著“You said:hello,world”。

Web 瀏覽器允許任何站點(diǎn)打開(kāi)與任何其他站點(diǎn)的 websocket 連接,而不是使用同源策略來(lái)管理來(lái)自 JavaScript 的其他網(wǎng)絡(luò)訪問(wèn)。 這可能令人驚訝并且是一個(gè)潛在的安全漏洞,因此由于 Tornado 4.0 ?WebSocketHandler要求希望接收跨域 websocket 的應(yīng)用程序通過(guò)覆蓋 ?check_origin方法來(lái)選擇加入。 不這樣做是建立 websocket 連接時(shí)最可能導(dǎo)致 403 錯(cuò)誤的原因。

當(dāng)使用帶有自簽名證書(shū)的安全 websocket 連接 (?wss://?) 時(shí),來(lái)自瀏覽器的連接可能會(huì)失敗,因?yàn)樗胍@示“接受此證書(shū)”對(duì)話框但無(wú)處顯示。 在 websocket 連接成功之前,您必須首先使用相同的證書(shū)訪問(wèn)常規(guī) HTML 頁(yè)面以接受它。

如果應(yīng)用程序設(shè)置 ?websocket_ping_interval的值為非零值,則會(huì)定期發(fā)送 ping,如果在 ?websocket_ping_timeout之前沒(méi)有收到響應(yīng),則連接將關(guān)閉。

大于 ?websocket_max_message_size應(yīng)用程序設(shè)置(默認(rèn) 10MiB)的消息將不被接受。

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

事件處理程序

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

打開(kāi)新的 WebSocket 時(shí)調(diào)用。

?open的參數(shù)是從?tornado.web.URLSpec正則表達(dá)式中提取的,就像tornado.web.RequestHandler.get的參數(shù)一樣。

?open可能是一個(gè)協(xié)程。 在 ?open返回之前不會(huì)調(diào)用 ?on_message?。

在 5.1 版更改: ?open可能是一個(gè)協(xié)程。

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

處理 WebSocket 上的傳入消息

必須重寫(xiě)此方法。

在 4.5 版更改: ?on_message?可以是協(xié)程。

WebSocketHandler.on_close() → None

當(dāng) WebSocket 關(guān)閉時(shí)調(diào)用。

如果連接完全關(guān)閉并且提供了狀態(tài)代碼或原因短語(yǔ),則這些值將作為屬性用于 ?self.close_code? 和 ?self.close_reason?

在 4.0 版更改: 添加了 ?close_code和 ?close_reason屬性。

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

重寫(xiě)以實(shí)現(xiàn)子協(xié)議協(xié)商。

?subprotocols是標(biāo)識(shí)客戶(hù)端提出的子協(xié)議的字符串列表。 可以重寫(xiě)此方法以返回其中一個(gè)字符串以選擇它,或者返回 ?None以不選擇子協(xié)議。

未能選擇子協(xié)議不會(huì)自動(dòng)中止連接,盡管如果沒(méi)有選擇任何建議的子協(xié)議,客戶(hù)端可能會(huì)關(guān)閉連接。

列表可能為空,在這種情況下,此方法必須返回 ?None?。 即使沒(méi)有提出子協(xié)議,該方法也總是只調(diào)用一次,以便可以將這一事實(shí)告知處理程序。

在 5.1 版更改: 以前,如果客戶(hù)端沒(méi)有提出子協(xié)議,則使用包含空字符串而不是空列表的列表調(diào)用此方法。

WebSocketHandler.selected_subprotocol

?select_subprotocol返回的子協(xié)議。

5.1 版中的新功能。

WebSocketHandler.on_ping(data: bytes) → None

在收到 ping 幀時(shí)調(diào)用。

輸出

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

將給定消息發(fā)送到此 Web Socket 的客戶(hù)端。

消息可以是字符串或字典(將被編碼為 json)。 如果 ?binary參數(shù)為 false,則消息將作為 utf8 發(fā)送; 在二進(jìn)制模式下,任何字節(jié)串都是允許的。

如果連接已經(jīng)關(guān)閉,則引發(fā) ?WebSocketClosedError?。 返回可用于流量控制的 ?Future?。

在 3.2 版更改:添加了 ?WebSocketClosedError?(以前關(guān)閉的連接會(huì)引發(fā) ?AttributeError?)

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

在 5.0 版更改: 持續(xù)引發(fā) ?WebSocketClosedError?。 以前有時(shí)會(huì)引發(fā) ?StreamClosedError?。

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

關(guān)閉此 Web Socket。

一旦關(guān)閉握手成功,套接字將被關(guān)閉。

?code可以是數(shù)字狀態(tài)代碼,取自 RFC 6455 第 7.4.1 節(jié)中定義的值。?reason?可能是關(guān)于連接關(guān)閉原因的文本消息。 這些值可供客戶(hù)端使用,但不會(huì)被 websocket 協(xié)議解釋。

在 4.0 版更改: 添加了?code?和?reason?參數(shù)。

配置

WebSocketHandler.check_origin(origin: str) → bool

覆蓋以啟用對(duì)允許備用來(lái)源的支持。

?origin參數(shù)是 ?OriginHTTP 表頭的值,即負(fù)責(zé)發(fā)起此請(qǐng)求的 url。 不發(fā)送此表頭的客戶(hù)端不會(huì)調(diào)用此方法; 這樣的請(qǐng)求總是被允許的(因?yàn)樗袑?shí)現(xiàn) WebSockets 的瀏覽器都支持這個(gè)表頭,并且非瀏覽器客戶(hù)端沒(méi)有相同的跨站點(diǎn)安全問(wèn)題)。

應(yīng)該返回 ?True來(lái)接受請(qǐng)求或 ?False來(lái)拒絕它。 默認(rèn)情況下,拒絕所有來(lái)自主機(jī)以外的主機(jī)的請(qǐng)求。

這是針對(duì)瀏覽器上的跨站點(diǎn)腳本攻擊的安全保護(hù),因?yàn)樵试S WebSocket 繞過(guò)通常的同源策略并且不使用 CORS 表頭。

注意:這是一項(xiàng)重要的安全措施;不要在不了解安全隱患的情況下禁用它。 特別是,如果您的身份驗(yàn)證是基于 cookie 的,您要么限制 ?check_origin() 允許的來(lái)源,要么為 websocket 連接實(shí)現(xiàn)類(lèi)似 XSRF 的保護(hù)。

要接受所有跨域流量(這是 Tornado 4.0 之前的默認(rèn)設(shè)置),只需重寫(xiě)此方法以始終返回 ?True?:

def check_origin(self, origin):
    return True

要允許來(lái)自您網(wǎng)站的任何子域的連接,您可以執(zhí)行以下操作:

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]]

覆蓋以返回連接的壓縮選項(xiàng)。

如果此方法返回 None(默認(rèn)值),壓縮將被禁用。 如果它返回一個(gè) dict(甚至是一個(gè)空的),它將被啟用。 dict 的內(nèi)容可用于控制以下壓縮選項(xiàng):

?compression_level指定壓縮級(jí)別。

?mem_level指定用于內(nèi)部壓縮狀態(tài)的內(nèi)存量。

WebSocketHandler.set_nodelay(value: bool) → None

為此流設(shè)置無(wú)延遲標(biāo)志。

默認(rèn)情況下,小消息可能會(huì)被延遲組合以最小化發(fā)送的數(shù)據(jù)包數(shù)量。 由于 Nagle 的算法和 TCP 延遲 ACK 之間的交互,這有時(shí)會(huì)導(dǎo)致 200-500 毫秒的延遲。 要減少此延遲(以可能增加帶寬使用為代價(jià)),請(qǐng)?jiān)诮?nbsp;websocket 連接后調(diào)用 ?self.set_nodelay(True)?。

其他

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

向遠(yuǎn)端發(fā)送 ping 幀。

data 參數(shù)允許作為 ping 消息的一部分發(fā)送少量數(shù)據(jù)(最多 125 個(gè)字節(jié))。 請(qǐng)注意,并非所有 websocket 實(shí)現(xiàn)都會(huì)將此數(shù)據(jù)公開(kāi)給應(yīng)用程序。

考慮使用 ?websocket_ping_interval應(yīng)用程序設(shè)置,而不是手動(dòng)發(fā)送 ping。

在 5.1 版更改: data 參數(shù)現(xiàn)在是可選的。

WebSocketHandler.on_pong(data: bytes) → None

在收到對(duì) ping 幀的響應(yīng)時(shí)調(diào)用。

exception tornado.websocket.WebSocketClosedError

由關(guān)閉連接上的操作引發(fā)。

客戶(hù)端支持

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]

接受一個(gè) url 并返回一個(gè) Future,其結(jié)果是一個(gè) ?WebSocketClientConnection?。
?compression_options ?的執(zhí)行方式與 ?WebSocketHandler.get_compression_options? 的返回值相同。
該連接支持兩種操作方式。 在協(xié)程風(fēng)格中,應(yīng)用程序通常會(huì)在循環(huán)中調(diào)用 ?read_message?:

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

在回調(diào)樣式中,將 ?on_message_callback ?傳遞給 ?websocket_connect?。 在這兩種樣式中,?None ?消息表示連接已關(guān)閉。

?subprotocols可以是指定提議的子協(xié)議的字符串列表。 當(dāng)連接完成時(shí),可以在連接對(duì)象的 ?selected_subprotocol屬性上找到所選協(xié)議。

在 3.2 版更改: 也接受 ?HTTPRequest對(duì)象代替 url。

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

在 4.5 版更改:添加了 ?ping_interval、?ping_timeout和 ?max_message_size參數(shù),它們與 ?WebSocketHandler中的含義相同。

在 5.0 版中更改: ?io_loop參數(shù)(自 4.1 版以來(lái)已棄用)已被刪除。

在 5.1 版更改: 添加了 ?subprotocols參數(shù)。

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 客戶(hù)端連接。

這個(gè)類(lèi)不應(yīng)該直接實(shí)例化; 請(qǐng)改用 ?websocket_connect ?函數(shù)。

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

關(guān)閉 websocket 連接。

?code?和?reason?記錄在 ?WebSocketHandler.close? 下。

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

向 WebSocket 服務(wù)器發(fā)送消息。

如果流已關(guān)閉,則引發(fā) ?WebSocketClosedError?。 返回可用于流量控制的 ?Future。

在 5.0 版更改:在關(guān)閉的流上引發(fā)的異常從 ?StreamClosedError?更改為 ?WebSocketClosedError?。

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

從 WebSocket 服務(wù)器讀取消息。
如果在 WebSocket 初始化時(shí)指定了 ?on_message_callback?,則此函數(shù)將永遠(yuǎn)不會(huì)返回消息
返回結(jié)果為?future?的消息,如果連接已關(guān)閉,則返回 ?None?。 如果給定了回調(diào)參數(shù),它將在準(zhǔn)備好時(shí)與?future?一起調(diào)用。

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

向遠(yuǎn)端發(fā)送 ping 幀。

?data參數(shù)允許作為 ping 消息的一部分發(fā)送少量數(shù)據(jù)(最多 125 個(gè)字節(jié))。 請(qǐng)注意,并非所有 websocket 實(shí)現(xiàn)都會(huì)將此數(shù)據(jù)公開(kāi)給應(yīng)用程序。

考慮對(duì) ?websocket_connect使用 ?ping_interval參數(shù),而不是手動(dòng)發(fā)送 ping。

selected_subprotocol

服務(wù)器選擇的子協(xié)議

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)