一、Http 協議無狀態的含義
1.1 有狀態協議
常見的許多七層協議實際上是有狀態的,例如 SMTP 協議,它的第一條消息必須是 HELO,用來握手,在 HELO 發送之前其他任何命令都是不能發送的;接下來一般要進行 AUTH 階段,用來驗證用戶名和密碼;接下來可以發送郵件數據;最后,通過 QUIT 命令退出。可以看到,在整個傳輸層上,通信的雙方是必須要時刻記住當前連接的狀態的,因為不同的狀態下能接受的命令是不同的;另外,之前的命令傳輸的某些數據也必須要記住,可能會對后面的命令產生影響。這種就叫做有狀態的協議。
1.2 為什么說 http 協議是無狀態協議
相反,為什么說 HTTP 是無狀態的協議呢?因為它的每個請求都是完全獨立的,每個請求包含了處理這個請求所需的完整的數據,發送請求不涉及到狀態變更。即使在 HTTP/1.1 上,同一個連接允許傳輸多個 HTTP 請求的情況下,如果第一個請求出錯了,后面的請求一般也能夠繼續處理(當然,如果導致協議解析失敗、消息分片錯誤之類的自然是要除外的)。可以看出,這種協議的結構是要比有狀態的協議更簡單的,一般來說實現起來也更簡單,不需要使用狀態機,一個循環就行了。
1.3 為什么不改進 http 協議使之有狀態
最初的 http 協議只是用來瀏覽靜態文件的,無狀態協議已經足夠,這樣實現的負擔也很輕(相對來說,實現有狀態的代價是很高的,要維護狀態,根據狀態來操作。)。隨著 web 的發展,它需要變得有狀態,但是不是就要修改 http 協議使之有狀態呢?是不需要的。因為我們經常長時間逗留在某一個網頁,然后才進入到另一個網頁,如果在這兩個頁面之間維持狀態,代價是很高的。其次,歷史讓 http 無狀態,但是現在是對 http 提出了新的要求,按照軟件領域的通常做法是,保留歷史經驗,在 http 協議上再加上一層實現我們的目的(“再加上一層,你可以做任何事”)。所以引入了其他機制來實現這種狀態的連接。
1.4 無狀態協議的優缺點
和許多人想象的不同,會話(Session)支持其實并不是一個缺點,反而是無狀態協議的優點,因為對于有狀態協議來說,如果將會話狀態與連接綁定在一起,那么如果連接意外斷開,整個會話就會丟失,重新連接之后一般需要從頭開始(當然這也可以通過吸收無狀態協議的某些特點進行改進);而 HTTP 這樣的無狀態協議,使用元數據(如 Cookies 頭)來維護會話,使得會話與連接本身獨立起來,這樣即使連接斷開了,會話狀態也不會受到嚴重傷害,保持會話也不需要保持連接本身。另外,無狀態的優點還在于對中間件友好,中間件無需完全理解通信雙方的交互過程,只需要能正確分片消息即可,而且中間件可以很方便地將消息在不同的連接上傳輸而不影響正確性,這就方便了負載均衡等組件的設計。
無狀態協議的主要缺點在于,單個請求需要的所有信息都必須要包含在請求中一次發送到服務端,這導致單個消息的結構需要比較復雜,必須能夠支持大量元數據,因此 HTTP 消息的解析要比其他許多協議都要復雜得多。同時,這也導致了相同的數據在多個請求上往往需要反復傳輸,例如同一個連接上的每個請求都需要傳輸 Host、Authentication、Cookies、Server 等往往是完全重復的元數據,一定程度上降低了協議的效率。
1.5 HTTP 協議是無狀態協議,這句話本身到底對不對?
實際上,并不全對。HTTP/1.1 中有一個 Expect: 100-Continue 的功能,它是這么工作的:
在發送大量數據的時候,考慮到服務端有可能直接拒收數據,客戶端發出請求頭并附帶 Expect: 100-Continue 的 HTTP 頭,不發送請求體,先等待服務器響應 服務器收到 Expect: 100-Continue 的請求,如果允許上傳,發送 100 Continue 的 HTTP 響應(同一個請求可以有任意個 1xx 的響應,均不是最后的結果 Response,只起到提示性作用);如果不允許,例如不允許上傳數據,或者數據大小超出限制,直接返回 4xx/5xx 的錯誤 客戶端收到 100 Continue 響應之后,繼續上傳數據可以看出,這實際上很明顯是一個有狀態協議的套路,它需要先進行一次握手,然后再真正發送數據。不過,HTTP 協議也規定,如果服務端不進行 100 Continue 的響應,建議客戶端在等待較短的時間之后仍然上傳數據,以達成與不支持 Expect: 100-Continue 功能的服務器的兼容,這樣可以算是“能有狀態則有狀態,否則回到無狀態的路上”,這樣說 HTTP 1.x 是無狀態的協議也是沒錯的。
至于 HTTP/2,它應該算是一個有狀態的協議了(有握手和 GOAWAY 消息,有類似于 TCP 的流控),所以以后說“HTTP 是無狀態的協議”就不太對了,最好說“HTTP 1.x 是無狀態的協議”
二、如何解決無狀態問題
HTTP 協議是無狀態的,無狀態意味著,服務器無法給不同的客戶端響應不同的信息。這樣一些交互業務就無法支撐了。Cookie 應運而生。
2.1 Cookie
cookie 的傳遞會經過下邊這 4 步:
Client 發送 HTTP 請求給 Server Server 響應,并附帶 Set-Cookie 的頭部信息 Client 保存 Cookie,之后請求 Server 會附帶 Cookie 的頭部信息 Server 從 Cookie 知道 Client 是誰了,返回相應的響應Cookie 中文翻譯是甜品,使用 Cookie 可以自動填寫用戶名、密碼等,是給用戶的一點甜頭。
Server 拿到 Cookie 后面,通過什么信息才能判斷是哪個 Client 呢?服務器的 SessionID。
2.2 Session
如果把用戶名、密碼等重要隱私都存到客戶端的 Cookie 中,還是有泄密風險。為了更安全,把機密信息保存到服務器上,這就是 Session。Session 是服務器上維護的客戶檔案,可以理解為服務器端數據庫中有一張 user 表,里面存放了客戶端的用戶信息。SessionID 就是這張表的主鍵 ID。
Session 信息存到服務器,必然占用內存。用戶多了以后,開銷必然增大。為了提高效率,需要做分布式,做負載均衡。因為認證的信息保存在內存中,用戶訪問哪臺服務器,下次還得訪問相同這臺服務器才能拿到授權信息,這就限制了負載均衡的能力。而且 SeesionID 存在 Cookie,還是有暴露的風險,比如 CSRF(Cross-Site Request Forgery,跨站請求偽造)。
如何解決這些問題呢?基于 Token 令牌鑒權。
2.3 Token
首先,Token 不需要再存儲用戶信息,節約了內存。其次,由于不存儲信息,客戶端訪問不同的服務器也能進行鑒權,增強了擴展能力。然后,Token 可以采用不同的加密方式進行簽名,提高了安全性。
Token 就是一段字符串,Token 傳遞的過程跟 Cookie 類似,只是傳遞對象變成了 Token。用戶使用用戶名、密碼請求服務器后,服務器就生成 Token,在響應中返給客戶端,客戶端再次請求時附帶上 Token,服務器就用這個 Token 進行認證鑒權。
Token 雖然很好的解決了 Session 的問題,但仍然不夠完美。服務器在認證 Token 的時候,仍然需要去數據庫查詢認證信息做校驗。為了不查庫,直接認證,JWT 出現了。
2.4 JWT
JWT 的英文全稱是 JSON Web Token。JWT 把所有信息都存在自己身上了,包括用戶名密碼、加密信息等,且以 JSON 對象存儲的。
JWT 長相是 xxxxx.yyyyy.zzzzz,極具藝術感。包括三部分內容
Header 包括 token 類型和加密算法(HMAC SHA256 RSA){ "alg": "HS256", "typ": "JWT"}
Payload傳入內容
{ "sub": "1234567890", "name": "John Doe", "admin": true}
Signature簽名,把 header 和 payload 用 編碼后"."拼接,加鹽 secret(服務器私鑰)。
HMACSHA256(UrlEncode(header) + "." + UrlEncode(payload), secret);
最終的 token 就是這樣一個字符串
eyJhbGciOiJIUzI1NiJ9
.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ
.yKOB4jkGWu7twu8Ts9zju01E10_CPedLJkoJFCan5J4;
給 Token 穿個外套
Authorization: Bearer;
這就是我們在請求 Header 里面看到的內容格式了。
責任編輯:Rex_08