如何理解mqtt用到的101交換協議?

image.png

使用過mqtt的同窗都知道,mqtt鏈接時,在Network面板中的status是101。html

Name Status Time
mqtt 101(Switching Protocols) Pending

那麼101(Switching Protocols)究竟是什麼意思呢?
這篇文章將帶你理解101交換協議是什麼,以及101交換協議運用的協議升級機制。前端

  • 101交換協議
  • 協議升級機制

101交換協議

HTTP的101交換協議意味着client向server發送的消息中包含了Upgrade請求頭,server會根據client發送的這個請求頭切換協議。node

server會在response中添加一個Upgrade響應頭,來指示server切換到的協議。web

用一句話來講就是:client經過在請求頭中添加Upgrade告訴server切換協議,server在響應頭中添加upgrade說明切換後的協議。算法

再簡單一點就是:客戶端告訴服務端去切換協議。api

General

Request URL: wss://foo.bar
Request Method: GET
Status Code: 101 Switching Protocols

Request Headers

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協議
...

Response Headers

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。

升級HTTP/1.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 Switching Protocols響應狀態,而且Upgrade頭中指明它切換後的協議。
  • 升級失敗:若是服務端不能升級鏈接,它會忽略Upgrade頭,而後返回一個常規的響應(例如 200 OK)

服務器發送完101狀態碼以後,它能夠當即開始使用新協議,而且進行與其餘協議的handshake。一旦鏈接創建完成,鏈接就變爲雙向管道,初始化升級的請求能夠再協議之上初始化。

協議升級機制的常規用法

Upgrade這個頭會用在哪些場景呢?並且與WebSocket鏈接有關的請求頭都有哪些呢?

  • 升級爲WebSocket鏈接
  • 與WebSocket鏈接有關的請求頭

    • Sec-WebSocket-Extensions
    • Sec-WebSocket-Key
    • Sec-WebSocket-Protocol
    • Sec-WebSocket-Version
    • Sec-WebSocket-Accept(只讀)
升級爲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鏈接有關的請求頭

下面這些請求頭是WebSocket升級過程當中包含的請求頭。與Upgrade和Connection頭不一樣,下面這些請求頭

Sec-WebSocket-Extensions

聲明一個或者多個協議級的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]

Sec-WebSocket-Key

向服務端提供客戶端有權升級爲WebSocket的信息。這個頭可用於不安全的HTTP想要升級時,爲了提供某種程度的保護,防止濫用。key的值使用在WebSocket規範中定義的算法生成,因此這並不保證安全性。

這個key是爲了放置非WebSocket的客戶端無心中進行websocket鏈接或者濫用。
本質上,這個key表明着:「是的,我確實是要開啓一個WebSocket鏈接的。」

這個頭會自動被使用它的客戶端添加,不能被XMLHttpRequest.setRequestHeader() 添加

Sec-WebSocket-Key: key

基於這個key,服務器會在響應的Sec-WebSocket-Accept頭中加一個基於這個key的計算數據。

Sec-WebSocket-Protocol

這個頭會聲明一個或者多個你想要使用的WebSocket協議。
請求頭髮送Sec-WebSocket-Protocol,響應頭也會返回Sec-WebSocket-Protocol。

Sec-WebSocket-Protocol: subprotocols

subprotocols包括如下這些協議:https://www.iana.org/assignme...

上面示例中的mqtt也在其中:mqtt | mqtt | [MQTT Version 5.0]

Sec-WebSocket-Version

做爲請求頭:
聲明客戶端使用的WebSocket協議版本。

Sec-WebSocket-Version: version

服務器與客戶端通訊的WebSocket協議版本:https://www.iana.org/assignme...

最經常使用的是13。
image

做爲響應頭:
若是服務器不支持WebSocket協議,會返回相似426(Upgrade Required)而且在Sec-WebSocket-Version頭中返回支持的WebSocket版本列表。若是不支持,不返回Sec-WebSocket-Version頭。

Sec-WebSocket-Version: supportedVersions
Sec-WebSocket-Accept

服務器與客戶端創建握手過程當中的響應頭。至多出現一次:

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

文章首發於前端公衆號:大大大前端

努力成爲優秀的前端開發工程師!

相關文章
相關標籤/搜索