大家好,這裡是 Bo2SS~真快,畢業過去一年了,公司又注入了新鮮的血液。部門裡有一個大前端新人培訓,自己斗膽報了名,做一個 HTTP 相關知識的分享。其實之前自己沒有系統地學習過 HTTP,所以提前了 2 個月準備這次分享。上週分享完後,據問卷反饋全員五星🌟好評,就在這裡記錄一下吧~
前言#
如題,今天要聊的是網路通信中 HTTP 和 HTTPS 的那些事兒。
Q:為什麼要分享這個主題?
-
🫡 生活中常見。HTTP 在整個互聯網是非常常見的,比如我們看劇、刷短視頻、面向 Google 編程,都会用到它。作為研發,我們有義務深入了解一下。
-
🤔 工作中常見。在我們工作中其實經常會遇到相關問題,比如在前後端接口聯調的時候,如果遇到了預期之外的情況,我們首先要去關注的,就是 HTTP 請求裡的一些信息。我們應該熟悉它的結構和一些規範。
-
📖 思想可借鑒。HTTP 它發展了 30 多年,3 個大版本,它的很多設計思路是值得我們在開發中去借鑒的。
Q:參考了哪些資料?
這次分享前我做了很多準備工作,主要參考的有:
-
極客時間上的《透視 HTTP 協議》課程。這個系列我可能也就聽了十吧遍吧,如果大家想深入了解更多的細節,可以去看看。作者還提供了可以實戰學習的倉庫chronolaw/http_study,通過它你可以很方便地搭建一個 Web 伺服器,然後通過瀏覽器去訪問裡面的資源來理解 HTTP。
-
小林 Coding。這是我關注比較久的一個公眾號,裡面有很多關於計算機基礎的圖解系列文章,現在也有了小林 Coding 網站版。
-
Bo2SS。這是我自己的公眾號,就在這裡啦👋。之前寫過 2 篇 HTTPS 相關的文章:
此外,文中也引用了一些第三方的圖片,就沒有一一列舉來源了,如有不當之處,歡迎聯繫。
Q:這次分享的目標?
-
🔍 快速定位 HTTP 問題。剛剛說到了我們在前後端聯調的時候,可能會經常遇到結果不符合預期的情況,我們應該首先能夠通過狀態碼去快速定位,這個問題是後端的還是前端的。
-
🥣 熟悉 HTTP 報文裡的常見頭字段。熟悉常見的頭字段後,我們不僅可以掌握 HTTP 的基本功能,還可以學到很多 HTTP 的設計思想。這個報文在哪裡可以看到呢?各端一般都會有抓包工具來查看。
-
🔐了解基本的加密知識。互聯網時代,用戶隱私、業務保密都是非常重要的。
🏁終極目標:看完這篇文章,你將擁有自行深入學習 HTTP 的能力了,比如通過 WireShark、Chrome、Telnet 工具,甚至可以去看 RFC 文檔,裡面幾乎包含了所有網路相關的重要資料。
➕一些資料:各工具的使用指南(WireShark、Chrome、Telnet),RFC 文檔匯總。
Q:這次要分享哪些內容?
也就是這次分享的大綱:
簡單來說,今天要聊的就是HTTP 和 HTTPS 是什麼,然後它們怎麼發展的。
好,下面進入正題:
什麼是 HTTP?#
HTTP 是什麼,又不是什么?#
HTTP 的全稱是 Hypertext Transfer Protocol,也就是超文本、傳輸、協議。我們從後往前解釋~
-
協議。什麼是協議,我們可以聯想我們的租房協議、三方協議,其實都是一樣的含義,協議的 “協”,代表有兩個以上的參與方,協議的 “議” 呢,代表約定和規範,約定你可以做什麼、不能做什麼。
-
傳輸。然後是傳輸,我們可以聯想快遞,專門在兩點之間傳輸,其關鍵有兩點。第一點是雙向性,我們可以寄快遞,也可以收快遞;第二點是傳輸過程可以有中轉,比如我們寄快遞,會經過快遞小哥、快遞公司、物流倉庫等,最後才會到達收件方,同時這些中間者也都遵守協議。
-
超文本。最後關於超文本,顧名思義就是超越了普通文本的文本。這裡我想問大家一個問題,超文本除了文字、圖片、音頻、視頻格式,還有一個最關鍵的格式,是什麼?對了,就是超鏈接。超鏈接能夠讓我們從一個 “超文本” 跳躍到另一個 “超文本”,讓我們的文本從以前的線性結構,變成了非線性的網狀結構。
一句話總結就是:“HTTP 是一個在計算機世界裡專門在兩點之間傳輸文字、圖片、音頻、視頻等超文本數據的約定和規範”。
這張圖是我們基本的 HTTP 通信過程中的參與者們。從這張圖,我們可以捋清 HTTP 它不是什么,從而對 HTTP 有一個更清晰的認識。
-
HTTP 不是實體,比如左邊的 Web 瀏覽器(發送方),右邊的 Web 伺服器(接收方)。
-
HTTP 不是互聯網,HTTP 傳輸的超文本資源是互聯網資源中的一部分。
-
HTTP 不是一門編程語言,但是 HTTP 支持各種編程語言來實現。
-
HTTP 不是HTML,HTTP 可以傳輸 HTML,HTML 是超文本的常見格式。
-
HTTP 不是一個孤立的協議。通常在 HTTP 的下方,會有一些底層協議支持,比如 TCP、IP、DNS 等等;在 HTTP 的上方,也有一些依賴於 HTTP 的協議,比如 WebSocket、HTTPDNS 等等。這些協議相互交織,構成了一個協議網,而 HTTP 則處於中心地位。
HTTP 世界全覽#
我們再對 HTTP 世界的全貌進行一下瀏覽,主要分為應用相關和理論相關。有了對 HTTP 通信鏈路更宏觀的認識,我們在定位問題的時候,能夠更清楚問題可能是由哪個環節導致的。
HTTP 相關應用#
我們從右往左看:
-
Internet - WWW:Internet 就是互聯網,裡面存儲著各種信息資源。WWW 是互聯網的一個子集,簡稱萬維網,它基於 HTTP,所以存儲的都是超文本資源,這些資源在互聯網中的占比大約在 90%。
-
Web Browser:Web 瀏覽器,是 HTTP 通信過程中的請求方,並可以顯示請求到的資源。
-
Web Server:Web 伺服器,是 HTTP 通信過程中的響應方,由它來管理網路資源。一般會將它分為硬體和軟體,硬體指物理伺服器、雲伺服器之類的機器,軟體指 Nginx、Apache 之類的應用程序。
-
CDN:Content Delivery Network,也就是內容分發網路。前面說到 HTTP 的傳輸過程可以有中轉,這裡 CDN 的定位就是中轉方,起到網路代理的作用。它可以緩存伺服器的資源,加快網路響應,還可以提供負載均衡、安全防護等能力。
-
Crawler:就是爬蟲。和 Web 瀏覽器類似,它也可以理解為是一種用戶代理,一般是給各大搜索引擎自動抓取數據、存入數據庫用的。
-
Others: 其它還有 HTML、Web 服務、WAF。Web 服務可以理解為運行在 Web 伺服器上的具體服務或者服務開發規範。WAF 的全稱是網路應用防火牆,它其實也是一種代理。
HTTP 相關理論#
同樣從右往左看,右邊的 HTTP/1.1、HTTPS、HTTP/2、HTTP/3 就是今天我們要聊的主要協議,左邊:
- TCP/IP:它代表的其實是一個協議棧,裡面包含了很多網路通信協議。
這裡我們把基於 TCP/IP 的 HTTP 通信過程與快遞運輸又做一個類比:
1)超文本 => MAC:左圖中左邊這一列,要傳輸的超文本從應用層一直到鏈接層,每經過一層都會被加上對應的頭,比如 HTTP 頭、TCP 頭、IP 頭、MAC 頭,這就像快遞的打包過程;
2)MAC => 超文本:左圖中右邊這一列,被傳輸的數據每經過一層則都會被去除一個頭,這就像快遞的拆包過程。
- URI - URL:URI(I - Identifier)是統一資源標識符,它又分為 URL 和 URN 兩種形式,但因為後者在互聯網世界並不常用,所以 URI 一般指的就是 URL。
1)URL(L - Locator):我們瀏覽器上方的地址就是 URL。
它的基本組成如上圖,我們可以先關注紅框部分:
-
scheme:最左邊的 scheme 代表協議,如 http、https、ftp 等等。注意這裡協議後面緊跟的
://
符號是固定的、必須的。 -
host:中間 host 是主機名,也叫域名,待會講 DNS 的時候再細說。
-
path:最後邊的 path 代表資源路徑。
Q:這裡有一個問題,圖中示例 URL 的域名是www.creatorseo.com/
嗎?
答案是否定的,最後面的斜杠/
屬於 path,它代表的是所訪問主機的根目錄,因為早期互聯網上的計算機大多是 UNIX 系統,所以這裡的路徑格式是採用的是 UNIX 上的文件路徑風格。
下面還有一張圖,這張圖是 URL 的完整格式。
比上圖還多了三個組成:
-
user@:我們可以在 URL 裡就填上用戶密碼信息,但因為安全原因已經不推薦使用它了。
-
?query:這個部分可以附加一些對資源的額外要求,以
?
開始,由多個鍵值對k=v
組成,每個鍵值對用&
連接。 -
#fragment:它代表一個片段標識,我們可以理解為資源內的一個錨,它是給客戶端使用的,不會發送給伺服器。平時我們在看一些博客時(如閱讀原文跳轉到我的博客網頁),點擊懸浮目錄中的某個標題做跳轉,就是用的這個部分。
💡 這裡還有兩個小提醒~一個是 host 後面還可以通過:port
指定端口。另一個就是一般聊到 URL 時,還會聊到escape 轉義和encode 編碼兩個概念,因為如果沒有它們,伺服器可能就無法正確地處理 URL。試著想一想如果 path 裡也有?
符號,伺服器該怎麼解析出 query 的起始位置呢?
-
escape - 轉義:針對特殊字符,我們一般會做轉義,直接將其轉換成 ASCII 碼的十六進制後再加一個
%
前綴,比如SPACE
對應%20
,?
對應%3F
; -
encode - 編碼:針對漢字等其它語言,我們一般會先進行 UTF-8 編碼,再轉義。如果你不信,可以試試把包含中文的 URL 複製粘貼到微信中(如閱讀原文跳轉到我的博客網頁)。
2)URN(N - Name):下面再回到 URI 的第二種形式 URN,它是通過命名空間加具體標識符的形式來標記資源,如urn:<NAMESPACE-IDENTIFIER>:<NAMESPACE-SPECIFIC-STRING>
。它在我們上網時不常用,但如果你去買書,可能會發現每本書的條形碼位置有一串字符,如ISBN xxx-x-xx-xxxx
,這其實就是 URN 的一種用法。
- DNS:全稱 Domain Name System,即域名系統,它是用來做域名解析的應用層協議,也就是將域名轉換為 IP 地址。
我們先來看看域名的結構,還是這張圖,這裡紅框部分就是域名,它是一個有層次的結構,用.
分隔,越靠右邊,層級越高。如從右到左,分別為頂級域名、二級域名、三級域名等等。
我們再來看看 DNS 的類型和 DNS 解析域名的步驟,如下圖:
1)DNS 類型。DNS 分為根 DNS、頂級 DNS、權威 DNS 和非權威 DNS。根 DNS 共有 13 組,遍布全球,它可以根據請求的頂級域名將 DNS 解析指定給下面對應的頂級 DNS。頂級 DNS 又根據二級域名指定權威 DNS,直到解析出域名對應的 IP。而一些大公司還會自建 DNS,又叫非權威 DNS,它們的分布更廣,比較知名的有 Google 的 8.8.8.8,Microsoft 的 4.2.2.1,還有 CloudFlare 的 1.1.1.1 等等。
2)DNS 解析域名步驟。實際的解析過程分為 4 步:系統首先會找 DNS 緩存,可能是瀏覽器裡的,也可能是系統裡的;如果找不到,再去查看 hosts 文件,裡面有我們自定義的域名 - IP 對應規則,Mac 下的 hosts 文件路徑為/etc/hosts
;如果匹配不到,再去問非權威 DNS,一般默認是走我們網路運營商指定的;如果還是沒解析出來,就要走根 DNS 的解析流程喽~
💡 這裡還有一些域名解析相關的常用命令(dig、host、nslookup
),如果你感興趣,可以去終端試一試~
1. DNS addressing process: dig www.baidu.com +trace @8.8.8.8
2. domain name <=> IP: host www.baidu.com
3. domain => IP: nslookup www.baidu.com
如果你知道用 WireShark,還可以通過filter: port 53
過濾出 DNS 解析相關的抓包。
- Proxy:就是代理。代理一般分為正向代理和反向代理,正向代理靠近客戶端,反向代理靠近伺服端。剛剛我們提到的 CDN 屬於反向代理,而我們訪問外網用的 VPN 就屬於正向代理。
HTTP 報文#
鋪墊了這麼多(實際上也是值得的),終於到了 HTTP 最重要的部分!
所謂 HTTP,超文本傳輸協議,其中最重要的部分其實是最後的協議,裡面約定了 HTTP 報文的格式和用法。
基本格式#
我們先來看看 HTTP 報文的基本格式,它可以簡單分為頭部和身體兩部分:
1)頭部:一般可以包含起始行部分,也就是頭部由 Start line 和 Header 組成。下圖展示了一次請求中,請求頭和返回頭的結構:
-
請求頭
-
Start line 由請求方法、URI、HTTP 版本、空格間隔符以及最後的換行符組成。
-
Header 由一個個的
key:value
鍵值對和最後的換行符組成,注意這裡:
前不能有多餘的空格,不信你用telnet
命令試試(Mac 上可以使用brew install telnet
來安裝telnet
,並且推薦搭配極客時間提供的實戰倉庫chronolaw/http_study來用)。
-
-
返回頭
-
Start line 由HTTP 版本、狀態碼、狀態碼對應解釋、空格間隔符以及最後的換行符組成。
-
Header 結構和請求頭一樣。
-
2)身體:一般根據業務來約定 body 的具體內容,它是可有可無的。
下面我們再來看看請求行中的請求方法和狀態碼具體有哪些~
請求方法#
HTTP/1.1 裡規定了八種請求方法,這裡把它們分成了常用和不常用兩類,另外還有一類是擴展的請求方法,注意這些方法都必須是大寫的形式。
這裡主要介紹下常用的請求方法:
-
GET 和 HEAD,用來獲取伺服器資源。兩者的區別在於 HEAD 只會獲取到頭部信息,GET 會獲取到完整的頭部和身體信息。所以如果你只是想確認某個資源存不存在或者只需要頭部信息,可以用 HEAD 請求,從而減少傳輸量。
-
POST 和 PUT,用來發送資源給伺服器。兩者的區別在於前者是在伺服器創建資源,類似數據庫的 CREATE 操作,後者是修改伺服器的資源,類似 UPDATE 操作。兩者比較類似,實際應用中 PUT 使用較少。
💡 說到請求方法,一般還會提到安全和幂等兩個概念:
-
安全:指的是對伺服器資源不會有實質的修改,所以上面提到的 GET 和 HEAD 是安全的。
-
幂等:指的是相同的操作執行多次後,結果是否相同,所以上面提到的 GET、HEAD 和 PUT 都是幂等的。
返回狀態碼(5 類)#
這次到了我們的第一個目標:🔍 透過狀態碼快速定位 HTTP 問題。
狀態碼一般分為5 大類:
1xx:提示信息類。一般是一次請求的中間狀態,比較少見。
2xx:成功類。這說明請求是符合預期的,也是我們最願意看到的。
3xx:重定向類。資源發生了變動,需要客戶端向另一個域名重發請求。
4xx:客戶端錯誤。看到這個就要想想請求報文是不是填錯了。
5xx:伺服端錯誤。看到這個就要找伺服端的同學確認問題原因了。
常見的具體狀態碼可參考下圖:
-
301:永久重定向,可以把請求的 URL 改一下了。
-
302:臨時重定向。
-
304:伺服器資源沒有變,所以重定向到了本地緩存。
-
401:未認證錯誤,一般與鑑權、登錄相關。
-
403:訪問被拒絕,可能訪問到了敏感信息。
-
404:資源沒找到,可能資源路徑寫錯了,也可能是沒有權限訪問(錯誤碼是伺服端自定義設置的,404 一般用於表示資源沒找到,但也可以延伸其用途,隱蔽具體原因)。
-
405:請求方法不被允許。
-
502:網關或代理返回的錯誤碼,一般是訪問網關或代理背後的伺服器出錯。
-
503:伺服器暫時不可用,請稍後重試。
💡 對於 400、500,它們是比較籠統的錯誤碼,有時候是作為兜底錯誤碼返回的,說明發生了未知的錯誤;有時候是因為伺服端不想暴露過多的細節返回的。總之伺服端在盡可能遵守公共認知的情況下,是可以自定義狀態碼的。
常見頭字段(8 種)#
很快,我們又到了本文的第二個目標:🥣 熟悉 HTTP 報文裡的常見頭字段。
首先,我們把頭字段分為 Request、Response、Universal 三大類,Universal 類裡又包含 Entity 子類。Request 類頭字段是請求方用的,Response 類頭字段是響應方用的,Universal 類頭字段是請求方和響應方都可以用的,Entity 類頭字段一般是用來描述 body 屬性的。
上圖列出了常見的頭字段們,看著眼花繚亂,別急,我們分功能來解釋。補充:填充色相同的頭字段們一般是搭配著使用的或者相關的。
下面,我們將頭字段們按照功能主要分為 8 種來解釋。
1)Body:body 屬性相關,可以是描述請求報文中的,也可以是描述響應報文中的。
提到 body 的類型,我們首先需要了解什麼是 MIME(Multipurpose Internet Mail Extensions,多用途互聯網郵件擴展)類型,它是從電子郵件系統中誕生的,現在也被用來描述 body 的類型。這裡有MIME 類型匯總,你點開鏈接看一下就會覺得它們很眼熟,比如application/json
、text/html
、text/javascript
…… 它的前半部分是一個大的類別,後半部分是具體的格式。
-
Accept
表示的是請求方可以接受的 body 類型,可能不止一個。 -
Content-Type
表示的是實際傳輸的 body 類型。
為了減少 body 的大小,我們一般會對它進行壓縮,常見的壓縮格式有 gzip、deflate 和 br,它們對 text 的壓縮效果很好。
-
Accept-Encoding
表示的是請求方可以支持的壓縮格式,可能不止一個。 -
Content-Encoding
表示的是傳輸的 body 實際採用的壓縮格式。
因為上面的壓縮方式一般只對文本有較好的壓縮率,對於壓縮效果不好的圖片、音視頻等多媒體格式,還有一種方式來解決大文件的問題,那就是分塊傳輸。
Transfer-Encoding: chunked
:這表示數據是被分塊傳輸的。
對於視頻類型的 body,比如我們在 b 站上看視頻,這個視頻不可能是一次就請求下來的,我們可以對視頻進行分段請求。
-
Accept-Ranges: bytes
:我們一般會通過 HEAD 請求先問問伺服端是否支持範圍請求,如果支持通過字節範圍請求,伺服端就會返回這個。 -
Range: bytes=x-y
:在伺服端支持的情況下,請求方就可以明確要請求第 x~y 字節的內容。 -
Content-Range: bytes x-y/length
:這表示伺服端返回的 body 是第 x~y 字節的內容,內容總長度為 length。
在國際化方面,我們還可以設置對語言的要求。
-
Accept-Language
表示的是請求方可以理解的語言,可能不止一個。 -
Content-Language
表示的是傳輸的 body 實際的語言。
這裡還要舉一個具體的例子:Accept-Language: en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7
,有兩個細節我們要注意:
-
在 HTTP 規範裡,
,
的優先級大於;
,這和我們一般的編程語言語法相反,所以上面的en;q=0.9
是一對。 -
上面的
q
是什麼?它其實代表一個權重,默認是 1,響應方會盡可能使用權重最大的語言返回內容。
2)Connection:長連接相關。
在 HTTP/1.1 之前,客戶端每次和伺服端通信都需要重新建立連接,如果頻繁通信,就會不斷地重複建立和關閉 TCP 連接,如下圖左邊所示,即短連接:
所以要是可以讓一次 TCP 連接保持久一點,每次連接兩端多通信幾次就好了,也就是圖中右邊所示,即長連接。這不,HTTP/1.1 就支持了。
-
Connection: keep-alive
:即表示使用長連接,在 HTTP/1.1 中默認開啟。 -
Connection: close
:主動關閉長連接,一般是由客戶端發出的。
對於伺服端,它也可以設置長連接的斷開時機,它們是在 Web 伺服器中配置的。比如在 Nginx 中,keepalive_timeout
代表長連接的超時時間,如果長時間沒有數據收發就主動斷開連接; keepalive_requests
代表長連接過程中最多接收請求多少次。
因為長連接的方式,客戶端也可以同時發起多個請求,而不必等第一個請求的結果回來再發第二個請求,這也就是管道通信。
不過不管短連接還是長連接,都還會有一個隊頭阻塞(HoL blocking)問題,這是因為 HTTP 的 “請求 - 應答” 模型規定報文必須是 “一發一收” 導致的,可以參考下圖:
無論如何,接收方都必須先處理完紅線請求後,才能處理後面發起的請求,即使後面的請求先到了,也就是 “先發必須先處理”。
為了緩解這個問題,有一個辦法,就是並發連接,也就是對一個域名發起多個長連接,每個長連接之間互不干擾。但是長連接的保持是需要消耗伺服器資源的,而且也可能被惡意攻擊,所以規定一般長連接的上限是 6~8 個。如果還不夠用,還有一個取巧的方式,就是域名分片,同一台伺服器有多個域名對應,那麼上限也就翻倍了。
3)Redirection:重定向相關。
在聊狀態碼時,我們提到過 301(永久)、302(臨時),不知道你還記不記得它們的含義。如果返回這種狀態碼,那麼在響應頭裡一定還會標識重定向的位置。
Location
就是重定向的位置,一般有絕對路徑和相對路徑兩種形式,絕對路徑對應的就是 URL 的基本格式,相對路徑則沒有 scheme 和 host,默認使用原請求的 URL 裡的。
和重定向相關的還有 3 種狀態碼:303 類似 302,但請求方法只能是 GET;307、308 分別類似 302、301(這裡是反的……),但它們都不允許重定向後的請求有任何變動。
4)Cookie:解決 HTTP 無狀態特點帶來的問題。
首先我們要清楚無狀態指的是客戶端還是伺服端?沒錯,是伺服端,也就是伺服端不知道收到的這次請求和上次請求有什麼關聯,那這樣會讓伺服端處理一些特殊場景時方案變得複雜,比如購物。
所以這裡 Cookie 就是來解決這個問題的。簡單來說,就是伺服端給客戶端貼了一個小紙條,標識某個客戶端的身份,這個客戶端在每次請求時帶上這個小紙條,就可以證明自己的身份了。
-
Set-Cookie: a=xxx
,Set-Cookie: b=yyy
:這是伺服端返回的,一個 Cookie 本質上就是一個鍵值對,並且每個 Cookie 是分開的。 -
Cookie: a=xxx; b=yyy
:這是客戶端在發送請求時帶上的,也就是之前伺服端返回的 Cookie 們,它們是合在一起的。
注意,客戶端在收到這些 Cookie 後會將它們保存到客戶端裡,我們去看看 Chrome 瀏覽器就可以發現它們。
咦?Cookie 除了 Name 和 Value,怎麼還有這麼多屬性?其實,伺服端返回的 Cookie 一般長這樣:Set-Cookie: a=xxx; Domain=xx; Path=xx; Max-Age=xx; Expires=xx; HttpOnly; Secure; SameSite=xx...
。
-
Domain、Path:只有在客戶端請求的 URL 匹配上它們時,這個 Cookie 才會被帶上。
-
Max-Age、Expires:代表 Cookie 的失效時間,後面 Cache 也有類似的屬性,要注意的是 Max-Age 的優先級大於 Expires。
-
HttpOnly:其為真時,代表這個 Cookie 只能通過 HTTP (S) 協議傳輸,禁止其他方式訪問,比如在 JS 裡就不再可以用 document.cookie 獲取它了,以防腳本攻擊。
-
Secure:其為真時,代表這個 Cookie 只有在發起安全的 HTTPS 請求時,才會被帶上。
-
SameSite=xxx:設置 SameSite=Strict 可以嚴格限定該 Cookie 不能跨站發送;SameSite=Lax 則略寬鬆一點,允許在 GET/HEAD 等安全的請求裡使用該 Cookie。
5)Cache:緩存相關。
緩存真的無處不在,HTTP 請求裡也不例外。這裡提到的緩存是存放在客戶端的,目的就是儘可能地減少網路請求或著返回數據的大小,從而提升網路傳輸效率。
-
Cache-Control
-
伺服端可以返回的屬性有:
max-age=10
/no-store
/no-cache
/must-revalidate
。-
max-age 的單位是秒,從返回那一刻就開始計算;
-
no-store 代表客戶端不允許緩存;
-
no-cache 代表客戶端使用緩存前必須先來伺服端驗證;
-
must-revalidate 代表緩存失效後必須驗證。
-
-
客戶端可以發送的屬性有:
max-age=0
;no-cache
。-
一般地,cmd + R 刷新頁面會帶上 max-age=0,意思是只要生存了 0 秒的數據,也就是不走本地緩存了,而是向伺服器要一個最新生成的報文;cmd + shift + R 強制刷新頁面會帶上 no-cache,和前者基本一致,看伺服端如何處理。
-
那什麼時候緩存會生效呢?一般是在瀏覽器前進、後退或者重定向時,客戶端發起的請求就不會帶上上面的兩個屬性了。
-
-
此外,為了增加緩存控制的靈活性,這裡還有一些條件字段~
-
伺服端返回的有:
-
Last-Modified
代表文件的最後修改時間。 -
ETag
全稱是 Entity Tag,代表資源的唯一標識,它是為了解決修改時間無法準確區分文件變化的問題。比如一個文件在一秒內修改了很多次,而修改時間的最小單位是秒;又或者一個文件修改了時間屬性,但內容沒有變化。Etag 還分為強 Etag、弱 Etag:-
前者不變的條件是資源在字節級別不變。
-
後者不變的條件是資源在語義上不變即可,比如多了幾個空格之類的。另外,弱 Etag 的值會在前面加一個
W/
標記。
-
-
-
客戶端請求時對應就用:
-
If-Modified-Since
裡放的就是上次請求伺服端返回的 Last-Modified,如果伺服端資源沒有比這個時間更新的話,伺服端就會返回 304,表示客戶端用緩存就行。 -
If-None-Match
裡放的就是上次請求伺服端返回的 ETag 了,如果伺服端資源的 Etag 沒變,伺服端也是返回 304。
-
6)Proxy:代理相關。
代理是具有雙重身份的,因為在客戶端眼裡,它是伺服端,而在伺服端眼裡,它又是客戶端。
前面也提到了代理一般分為正向代理和反向代理,反向代理一般被用來做負載均衡(合理分配任務,決定由後面的哪台伺服器來響應請求)、安全防護、加密卸載(在內網裡通信就不加密了,減少加解密成本)、內容緩存(暫存伺服器響應,這個下面會說,也就是代理緩存)等等。
在上面有代理伺服器的場景中,涉及到的頭字段是:
Via
:代理伺服器會在發送請求時,把自己的主機名加端口信息追加到該字段的末尾。
但是伺服器一般需要知道客戶端的真實 IP 信息,方便做訪問控制、用戶畫像、統計分析等,所以在 HTTP 標準外還約定了下面的頭字段:
-
X-Forwarded-For
:類似 Via 的追加方式,但追加的內容是請求方的 IP 地址。 -
X-Real-IP
:只記錄客戶端的 IP 地址,它更簡潔一點。
不過上面的方式其實有一個很大的缺點:損耗性能!因為每次代理伺服器需要解析出 HTTP 報文頭,再修改報文數據;而且在有些情況下,報文是不允許甚至是不能(加密)被修改的。所以後來就又出現了一個專門的代理協議,這也是在標準外約定的。
基於這個協議,代理伺服器只需要在 HTTP 報文前再加一行文本即可。比如:
PROXY TCP4 1.1.1.1 2.2.2.2 55555 80\r\n
GET / HTTP/1.1\r\n
Host: www.xxx.com\r\n
\r\n
-
開頭是
PROXY
五個大寫字母; -
然後是客戶端的 IP 地址類型,如
TCP4
或者TCP6
; -
再後面是請求方、應答方的地址,以及請求方、應答方的端口號;
-
最後用一個回車換行結束。
7)Proxy Cache:代理緩存相關。
客戶端可以緩存,中間商代理伺服器當然也可以緩存。但因為代理的雙重身份性,所以Cache-Control
針對代理緩存還增加了一些定制化的屬性~
-
從伺服端到代理伺服器:
private
代表數據只能在客戶端保存,不能緩存在代理上與別人共享,比如用戶的私人數據。public
代表數據完全開放,誰都可以緩存。s-maxage
代表緩存在代理伺服器上的生存時間。no-transform
代表禁止代理伺服器對數據做一些轉換操作,因為有的代理會提前對數據做一些格式轉換,方便後面的請求處理。
-
從客戶端到代理伺服器:
-
max-stale
代表接受緩存過期一段時間。 -
min-fresh
則與上面相反,代表緩存必須還有一段時間的保質期。 -
only-if-cached
代表客戶端只接受代理緩存。如果代理上沒有符合條件的緩存,客戶端也不要代理再去請求伺服端了。
-
8)Others
我們再看看這張常見頭字段圖,你是不是已經清楚每一個頭字段的含義和用法了呢~
等等,上面還漏了一些頭字段的說明,我把它們統一放在這裡補充一下:
-
Host
代表要請求的主機名。它在 HTTP/1.1 裡是必須出現的,用來給伺服器區分具體選擇哪個主機來處理請求的(如果計算機上托管了多個虛擬主機就有這個作用,否則伺服器一般也不會去處理)。另外,在一般的網路框架裡,它會幫我們從 URL 裡解析出一個默認的 Host 值兜底,所以可能你沒有手動填也沒有問題,因為框架默認幫我們補充了。 -
User-Agent
即用戶代理,它是用來描述請求方的身份的,伺服器可以根據它來返回合適的頁面佈局或者數據。不過由於歷史原因,它的用法已經有些混亂了,比如每個瀏覽器都自稱是 “Mozilla Chrome Safari” 之類的。 -
Date
代表報文創建的時間,一般出現在響應頭裡。 -
Server
展示的是提供 Web 服務的軟體名稱和版本號,但這樣暴露了伺服器的部分信息,可能存在安全隱患,所以有的時候返回裡沒有這個字段,或者僅僅是一個模糊的信息。 -
Content-Length
代表報文裡 body 的長度。如果沒有這個字段,那麼一般會有另一個字段Transfer-Encoding: chunked
,前面我們說到過它。
至此,我們已經講完了什麼是 HTTP 了,可以試著用 Chrome 開發者工具或者 WireShark 抓包加深理解。
什麼是 HTTPS?#
HTTPS 比 HTTP 多了個 S,這個 S 代表的是 SSL/TLS 協議。
現在來到了我們第三個目標:🔐了解基本的加密知識。
這一節因為之前寫過相關文章,所以我盡量減少篇幅,你可以點擊下面的鏈接參考:
-
信息安全 | 互聯網時代,如何建立信任?:三大常見密碼學算法,數字簽名,數字證書。
-
信息安全 | (加餐)互聯網時代,如何建立信任?:SSL/TLS,SSH,iOS 簽名,OpenSSL、WireShark 實踐。
要補充的內容是:基於ECDHE的 TLS 主流握手方式 VS. 基於RSA的 TLS 傳統握手方式。
兩者的關鍵區別在於通信密鑰生成過程中,第三個隨機數Pre-Master
的生成方式:
-
前者:兩端先隨機生成公私鑰,同時公鑰(加簽名)作為參數傳給對方,然後兩端基於雙方的參數,使用 ECDHE 算法生成
Pre-Master
; -
後者:客戶端直接生成隨機數
Pre-Master
,然後用伺服器證書的公鑰加密後發給伺服器。
因為前者的公私鑰是隨機生成的,即使某次私鑰洩漏了或者被破解了,也只影響一次通信過程;而後者的公私鑰是固定的,只要私鑰洩漏或者被破解,那之前所有的通信記錄密文都會被破解,因為耐心的黑客一直在長期收集報文,等的就是這一天(據說斯諾登的稜鏡門事件就是利用了這一點)。
也就是說,前者 “一次一密”,具備前向安全;而後者存在 “今日截獲,明日破解” 的隱患,不具備前向安全。
更多的細節可以參看《透視 HTTP 協議》裡TLS1.2 連接過程解析這一課,或者自己用 WireShark 抓包試一試~
兩者從抓包上的區別來看,主要是:
-
前者比後者多了一個 “Server Key Exchange” 消息。
-
前者客戶端可以不等連接完全建立就提前進行加密通信,也就是客戶端不用等伺服器發回 “Finished” 確認握手完畢,這個叫 “TLS False Start”。
HTTP 的發展#
我們通過下面表格梳理一下 HTTP 的發展過程,今天我們對 HTTP 發展有一個整體的認知就好~
Time | Version | Main change | Note |
---|---|---|---|
1989 | 3 key technologies | HTML, URI, HTTP | Paper from Tim Berners-Lee. |
1991 | HTTP/0.9 | 1. Request way: GET. 2. Data: HTML. | No RFC. |
1996 | HTTP/1.0 | 1. +Request way: HEAD, POST. 2. +Data: img, audio. 3. +Other: HTTP Head, status code, protocol version. | RFC-1945 (1996). Not a formal standard. |
1999 | HTTP/1.1 | 1. +Request way: PUT, DELETE. 2. +Cache-control. 3. +Keep-Alive. 4. +Pipeline transmission(Content-Length), chunked transmission. 5. +Host head (Required). | +Google, Sina, Sohu, Netease, Tencent. RFC-2616 (1999). +Facebook, Twitter, Taobao, JD. Divided to RFC-7230~7235 (2014). RFC-9112 (2022). |
2015 | HTTP/2 | 1. Transmission data format: text → binary data. 2. +Concurrent requests (use stream, abandon pipeline transmission). 3. +Header Compression. 4. +Allow the server to push. 5. +Combined with TLS 1.2+. | Based on SPDY in Chrome browser (2009). RFC-7540 (2015). RFC-9113 (2022). |
2022 | HTTP/3 | 1. Transport layer protocol: TCP → QUIC (based on UDP, including TLS 1.3, IP → connection ID). 2. Header Compression: HPACK→QPACK | Based on QUIC in Chrome browser(2012). RFC-9114 (2022). |
-
從 HTTP/1.0 開始,HTTP 已經被寫入 RFC 文檔(RFC 文檔匯總)。
-
HTTP/1.1 是 HTTP 第一個正式標準,大多數的功能我們在常見頭字段那一節介紹了。在這個階段早期,Google、Sina、Sohu、Netease、Tencent 等公司被創立了,後期 Facebook、Twitter、Taobao、JD 等公司慢慢也出來了。
-
HTTP/1.1 在各方面還比較完善了,但在性能和安全上還存在很大優化空間。所以 HTTP/2、HTTP/3 都主要是針對 HTTP 的性能方面做優化。
- HTTP/2基於 Chrome 的 SPDY 協議,它是 Chrome 推動的。主要的變化有:
-
傳輸數據格式從文本轉成了二進制,大大方便了計算機的解析。
-
基於虛擬流的概念,實現了多路復用能力,同時替代了 HTTP/1.1 裡的管道功能。
-
利用 HPACK 算法進行頭部壓縮,在之前都只針對 body 做壓縮。
-
允許伺服端新建 “流” 主動推送消息。比如在瀏覽器剛請求 HTML 的時候就提前把可能會用到的 JS、CSS 文件發給客戶端。
-
在安全方面,其實也做了一些強化,加密版本的 HTTP/2 規定其下層的通信協議必須在 TLS1.2 以上(因為之前的版本有很多漏洞),需要支持前向安全和 SNI(Server Name Indication,它是 TLS 的一個擴展協議,在該協議下,在握手過程開始時通過客戶端告訴它正在連接的伺服器的主機名稱),並把幾百個弱密碼套件給列入 “黑名單” 了。
-
PS:相比 HTTP/1.1 中的並發連接方式,虛擬流的概念更優美地解決了 HTTP 的隊頭阻塞問題。
- HTTP/3基於 Chrome 的 QUIC 協議,它也是 Chrome 推動的。
-
先看看 QUIC
-
基於 UDP 實現了可靠傳輸,引入了類似 HTTP/2 的流概念。
-
內含了 TLS1.3,加快了建連速度。
-
連接使用 “不透明” 的連接 ID 來標記兩端,而不再通過 IP 地址和端口綁定,從而支持用戶無感的連接遷移。
-
-
回到 HTTP/3
-
它最大的改變就是把下層的傳輸層協議從 TCP 換成了 QUIC,完全解決了 TCP 的隊頭阻塞問題(注意,是 TCP 的,不是 HTTP 的),在弱網環境下表現更好。因為 QUIC 本身就已經支持了加密、流和多路復用等能力,所以 HTTP/3 的工作減輕了很多。
-
頭部壓縮算法從 HPACK 升級為 QPACK。
-
2022 年 6 月 6 日,HTTP/3 被正式寫入 RFC 文檔,同時,HTTP/1.1 和 HTTP/2 也更新了 RFC 文檔。
-
-
PS:TCP 為了保證可靠傳輸,有個特別的 “丟包重傳” 機制,即丟失的包必須要等待重新傳輸確認,其他的包即使已經收到了,也只能放在緩衝區(kernel)裡,上層的應用(user)拿不出來,可參考下圖:紅色方塊請求是阻塞 TCP 的關鍵。
(其實這裡有一點疑惑的是,合著 HTTP/3 之前解決的都只是 kernel 到 user 的阻塞問題?具體地說只是經過 TCP 之後的阻塞問題,在這個階段的阻塞問題有哪些呢🤔?歡迎大佬們答疑解惑~)
HTTPS 的發展#
這部分聊聊 TLS1.2 到 TLS1.3 的發展,在此之前的版本因為各種安全問題都已經被廢棄了,我們可以從信息安全 | (加餐)互聯網時代,如何建立信任?這篇文章裡了解到。
對於 TLS1.3 來說,它的主要優化目標有 3 個:
-
兼容 TLS1.2。為了保證老設備能夠更輕鬆地升級協議,TLS1.3 保持原有的記錄格式不變,利用擴展協議在原有記錄末尾增加一些 “擴展字段” 來增加新的功能,老版本的 TLS 不認識它們可以直接忽略,從而實現了 “後向兼容”。
-
更安全。TLS1.3 基於安全考慮對支持的密碼套件進行了瘦身,最終只剩下 5 個密碼套件。前面提到的基於 RSA 的 TLS 傳統握手方式就被廢除了。
-
更高性能。HTTPS 建立連接的過程除了 TCP 握手,還有 TLS 握手,在 TLS1.2 中 TLS 握手需要花費 2-RTT,而這個時間在 TLS1.3 中被優化到了 1-RTT。怎麼做的呢?
-
答案就在上一點中,因為密碼套件只有這麼多,TLS1.3 可以索性在 ClientHello 消息中帶上所有支持的密碼套件相關參數,伺服端選定其中一種就可以直接生成通信密鑰進行加密通信了!而客戶端也省去了 TLS1.2 中要等伺服端確認密碼套件後再發參數的過程。
-
除了標準的 1-RTT 握手,TLS1.3 在之前建立過連接從而緩存了伺服端密碼套件參數的情況下,還可以達到 0-RTT 握手,但這也存在前向不安全和重放攻擊的問題,所以需要使用者去權衡。
-
下面我再放上《透析 HTTP 協議》裡的通信過程對比圖,你可以再看看它們的差異。
-
尾聲(好消息)#
好啦,今天就聊到這裡啦,回到我們的目標,看看你還能想到具體的知識嗎?
-
🔍 快速定位 HTTP 問題。
-
🥣 熟悉 HTTP 報文裡的常見頭字段。
-
🔐了解基本的加密知識。
當然,別忘了我們的終極目標🏁:如果你對 HTTP 感興趣,試著通過 WireShark、Chrome、Telnet 工具以及 RFC 文檔,去自行深入學習 HTTP 吧~
這裡是 Bo2SS,咱們下次再見!
插播一條好消息,經過一年多的佛系經營,在 2022 年 8 月 14 日 10 時 45 分,Bo2SS 的粉絲基數悄悄突破 500 啦🎉!大家快來幫忙想想怎麼慶祝(薅)一下 Bo2SS?歡迎投票,或在下方留言!
投票:
A. 發發紅包,沾沾運氣
B. 贈送書本,汲取知識
C. 建交流群,促進友誼
D. 別玩了,繼續出文章
E. 都可以,不要白不要
F. 都不行,我去留言啦