使用過mqtt的同窗都知道,mqtt鏈接時,在Network面板中的status是101。html
Name | Status | Time |
---|---|---|
mqtt | 101(Switching Protocols) | Pending |
那麼101(Switching Protocols)究竟是什麼意思呢?
這篇文章將帶你理解101交換協議是什麼,以及101交換協議運用的協議升級機制。前端
HTTP的101交換協議意味着client向server發送的消息中包含了Upgrade請求頭,server會根據client發送的這個請求頭切換協議。node
server會在response中添加一個Upgrade響應頭,來指示server切換到的協議。web
用一句話來講就是:client經過在請求頭中添加Upgrade告訴server切換協議,server在響應頭中添加upgrade說明切換後的協議。算法
再簡單一點就是:客戶端告訴服務端去切換協議。api
Request URL: wss://foo.bar Request Method: GET Status Code: 101 Switching Protocols
Connection: Upgrade Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits Sec-WebSocket-Key: xxx Sec-WebSocket-Protocol: mqtt Sec-WebSocket-Version: 13 Upgrade: websocket // client告訴server使用websocket協議 ...
connection: Upgrade sec-websocket-accept: fNs9ByuvC+rD75+tj2GMQAzbJms= // server基於client發出的Sec-WebSocket-Key:xxx計算得出,計算過程文章末尾有介紹 sec-websocket-protocol: mqtt Upgrade: websocket // server告訴client,咱們(client,server)使用的是websocket協議 ...
其中這些請求頭是什麼意思呢?Connection,Sec-WebSocket-Extensions,Sec-WebSocket-Key,Sec-WebSocket-Protocol,Sec-WebSocket-Version等等。
響應頭呢?sec-websocket-accept,sec-websocket-protocol。安全
看了下面的協議升級機制就明白了。服務器
HTTP1.1版本的協議有一個特殊的機制:升級一個已經創建的鏈接爲另一個協議,通常是經過Upgrade頭來實現。websocket
這個機制是可選的,它不能強制協議改變。雖然實現支持新協議,可是也能夠選擇不升級。在實際應用中,一般這個機制用於引導WebSocket進行鏈接。socket
注意,HTTP2.0版本明確禁止使用這個機制。只能用於HTTP1.1。
client可使用Upgrade頭去邀請服務器去切換爲協議列表中的某一項,按照降序。
由於Upgrade是一個逐跳頭,所以它須要一個Connection頭。
這也就意味着一個典型的包含Upgrade報文頭的請求爲:
GET /index.html HTTP/1.1 Host: www.example.com Connection: upgrade Upgrade: example/1, foo/2
其餘的頭通常是依賴請求協議的;例如,WebSocket升級容許額外的頭去配置WebSocket鏈接,並在打開時就有必定的安全性。
若是服務器決定去升級鏈接,分爲升級成功和升級失敗兩種狀況:
服務器發送完101狀態碼以後,它能夠當即開始使用新協議,而且進行與其餘協議的handshake。一旦鏈接創建完成,鏈接就變爲雙向管道,初始化升級的請求能夠再協議之上初始化。
Upgrade這個頭會用在哪些場景呢?並且與WebSocket鏈接有關的請求頭都有哪些呢?
與WebSocket鏈接有關的請求頭
最多見的升級HTTP鏈接的場景,就是使用WebSockets的場景,一般是經過升級HTTP或者HTTPS鏈接的方式來實現。若是你使用WebSocket API去開啓一個鏈接,或者任何WebSockets的庫,大多數或者說全部的事情都已經爲你作好了。
例如:創建一個WebSocket鏈接很是簡單,只須要這樣既可:
webSocket = new WebSocket("ws://destination.server.ext", "optionalProtocol")
WebSockek()
構造函數爲開發者在內部作了全部建立一個HTTP/1.1鏈接,握手和升級的事情。
能夠用"wss://" 去創建一個安全的WebSocket鏈接。
若是你想本身手動創建一個WebSocket鏈接的話,你須要本身去處理握手過程。在建立完HTTP/1.1會話以後,你須要在請求上添加Upgrade和Connection這兩個請求頭。
Connection: Upgrade Upgrade: websocket
下面這些請求頭是WebSocket升級過程當中包含的請求頭。與Upgrade和Connection頭不一樣,下面這些請求頭
聲明一個或者多個協議級的WebSocket擴展區告訴服務器使用。在一個請求裏使用一個或者多個Sec-WebSocket-Extension頭是能夠的;放在一塊兒用分號隔開也能夠。
Sec-WebSocket-Extensions: extensions
extensions須要用分號分開。須要從插件列表裏選擇。
例如:Sec-WebSocket-Extensions: superspeed, colormode; depth=16
咱們上面例子中的permessage-deflate也在其中。
permessage-deflate | WebSocket Per-Message Deflate | [RFC7692] | None | [RFC7692]
向服務端提供客戶端有權升級爲WebSocket的信息。這個頭可用於不安全的HTTP想要升級時,爲了提供某種程度的保護,防止濫用。key的值使用在WebSocket規範中定義的算法生成,因此這並不保證安全性。
這個key是爲了放置非WebSocket的客戶端無心中進行websocket鏈接或者濫用。
本質上,這個key表明着:「是的,我確實是要開啓一個WebSocket鏈接的。」
這個頭會自動被使用它的客戶端添加,不能被XMLHttpRequest.setRequestHeader() 添加
Sec-WebSocket-Key: key
基於這個key,服務器會在響應的Sec-WebSocket-Accept頭中加一個基於這個key的計算數據。
這個頭會聲明一個或者多個你想要使用的WebSocket協議。
請求頭髮送Sec-WebSocket-Protocol,響應頭也會返回Sec-WebSocket-Protocol。
Sec-WebSocket-Protocol: subprotocols
subprotocols包括如下這些協議:https://www.iana.org/assignme...
上面示例中的mqtt也在其中:mqtt | mqtt | [MQTT Version 5.0]
做爲請求頭:
聲明客戶端使用的WebSocket協議版本。
Sec-WebSocket-Version: version
服務器與客戶端通訊的WebSocket協議版本:https://www.iana.org/assignme...
最經常使用的是13。
做爲響應頭:
若是服務器不支持WebSocket協議,會返回相似426(Upgrade Required)而且在Sec-WebSocket-Version頭中返回支持的WebSocket版本列表。若是不支持,不返回Sec-WebSocket-Version頭。
Sec-WebSocket-Version: supportedVersions
服務器與客戶端創建握手過程當中的響應頭。至多出現一次:
Sec-WebSocket-Accept: hash
若是有Sec-WebSocket-Key,將字符串「 258EAFA5-E914-47DA-95CA-C5AB0DC85B11」鏈接到該字符串,而且取SHA-1的20位 hash值。最後進行base64編碼。
在咱們的例子中:
Sec-WebSocket-Key: xxx Sec-WebSocket-Accept: fNs9ByuvC+rD75+tj2GMQAzbJms=
經過Sec-WebSocket-Key生成Sec-WebSocket-Accept的編碼過程以下:
const SecWebSocketKey = "xxx" const helper = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" const result = SecWebSocketKey + helper const crypto = require('crypto') const shasum = crypto.createHash('sha1') shasum.update(result) const SecWebSocketAccept = shasum.digest('base64'); console.log(SecWebSocketAccept) // fNs9ByuvC+rD75+tj2GMQAzbJms=
在線demo:https://www.jdoodle.com/ia/e3G
既然key到accept的算法已經很明晰了,那麼能夠經過accept反向求解出key嗎?
答案是否認的。這是由於用到了sha1加密。
密碼強哈希函數有兩個特色:其中一個很重要的特色就是不可逆。不可逆性意味着原始數據沒法從其散列中重建,因此不能經過accept反向求解出key。
參考資料:
https://developer.mozilla.org...
https://developer.mozilla.org...
https://stackoverflow.com/que...
https://nodejs.org/api/buffer...
https://stackoverflow.com/que...
文章首發於前端公衆號:大大大前端
努力成爲優秀的前端開發工程師!