最近看了http相關的知識點,覺得還是有必要整理下,這樣對自己的網路知識體系也有幫助。
HTTP 是什麼
協議
http 是一個用在計算機裡面的協議,使用計算機通訊之間的一個規範,以及相關各種控制和錯誤處理方式。簡單的說就是約定、規則。
傳輸
http 是一個在計算機世界裡專門用來在計算機之間傳輸資料的約定和規範。
超文字
所謂“超文字”,就是“超越了普通文字的文字”,它是文字、圖片、音訊和視訊等的混合體,最關鍵的是含有“超連結”,能夠從一個“超文字”跳躍到另一個“超文字”,形成複雜的非線性、網狀的結構關係
http 不是一個孤立的協議
http 是一個應用層協議, 通常跑在 TCP/IP 協議棧之上,依靠 IP 協議實現定址和路由、TCP 協議實現可靠資料傳輸、DNS 協議實現域名查詢、SSL/TLS 協議實現安全通訊。此外,還有一些協議依賴於 HTTP,例如 WebSocket、HTTPDNS 等。
HTTP 優缺點
特點
靈活可擴充套件
語法上只規定了基本格式,空格分隔單詞,換行分隔欄位等。以及傳輸上不僅可以傳輸文字,還可以傳輸圖片,視訊等任意資料。
可靠傳輸
請求-應答模式
請求 - 應答模式是 HTTP 協議最根本的通訊模型,簡單的理解就是,一方發訊息,一方接收訊息
無狀態
整個協議裡沒有規定任何的“狀態”,客戶端和伺服器永遠是處在一種“無知”的狀態。建立連線前兩者互不知情,每次收發的報文也都是互相獨立的,沒有任何的聯絡。
缺點
不安全
明文傳輸,其次HTTP 協議也不支援“完整性校驗”,資料在傳輸過程中容易被竄改而無法驗證真偽。為了解決 HTTP 不安全的缺點,所以就出現了 HTTPS
效能
http 是請求-應答模式,發起的請求類似一個佇列,先進先出,會存在對頭阻塞問題
無狀態
這個無狀態主要看應用場景,對於不需要上下文狀態的,這個就是優點,對於需要記錄狀態的,這個就是缺點,當然這個可以通過其他方式來做,比如 cookie,token 等
HTTP 報文結構
HTTP 協議的請求報文和響應報文的結構基本相同,由三大部分組成:
- 起始行(start line):描述請求或響應的基本資訊
- 頭部欄位集合(header):使用 key-value 形式更詳細地說明報文
- 訊息正文(entity):實際傳輸的資料,它不一定是純文字,可以是圖片、視訊等二進位制資料
前兩部分起始行和頭部欄位經常又合稱為“請求頭”或“響應頭”,訊息正文又稱為“實體”,但與“header”對應,很多時候就直接稱為“body”。
HTTP 協議規定報文必須有 header,但可以沒有 body,而且在 header 之後必須要有一個“空行”(區分body 與 head 分割),也就是CRLF。
HTTP 頭欄位非常靈活,不僅可以使用標準裡的 Host、Connection 等已有頭,也可以任意新增自定義頭,這就給 HTTP 協議帶來了非常好的靈活、擴充套件性。
請求行
報文裡的起始行也就是請求行(request line),它簡要地描述了客戶端想要如何操作伺服器端的資源。
狀態行
這裡它不叫“響應行”,而是叫“狀態行”(status line),意思是伺服器響應的狀態。
頭部欄位
HTTP 常見狀態碼
RFC 規定 HTTP 的狀態碼為「三位數」,第一個數字定義了響應的類別,被分為五類:
- 1xx: 代表請求已被接受,需要繼續處理。
- 2xx: 表示成功狀態。
- 3xx: 重定向狀態。
- 4xx: 客戶端錯誤。
- 5xx: 伺服器端錯誤。
客戶端作為請求的發起方,獲取響應報文後,需要通過狀態碼知道請求是否被正確處理,是否要再次傳送請求,如果出錯了原因又是什麼。這樣才能進行下一步的動作,要麼傳送新請求,要麼改正錯誤重發請求。
伺服器端作為請求的接收方,也應該很好地運用狀態碼。在處理請求時,選擇最恰當的狀態碼回覆客戶端,告知客戶端處理的結果,指示客戶端下一步應該如何行動。特別是在出錯的時候,儘量不要簡單地返 400、500 這樣意思含糊不清的狀態碼。
目前 RFC 標準裡總共有 41 個狀態碼,但狀態碼的定義是開放的,允許自行擴充套件。所以 Apache、Nginx 等 Web 伺服器都定義了一些專有的狀態碼。如果你自己開發 Web 應用,也完全可以在不衝突的前提下定義新的程式碼。
1xx 資訊類
接受的請求正在處理,資訊類狀態碼。
2xx 成功
- 200 OK 表示從客戶端發來的請求在伺服器端被正確請求。
- 204 No content,表示請求成功,但沒有資源可返回。
- 206 Partial Content,該狀態碼錶示客戶端進行了範圍請求,而伺服器成功執行了這部分的 GET 請求 響應報文中包含由 「Content-Range」 指定範圍的實體內容。
3xx 重定向
- 301 moved permanently,永久性重定向,表示資源已被分配了新的 URL,這時應該按 Location 首部欄位提示的 URI 重新儲存。
- 302 found,臨時性重定向,表示資源臨時被分配了新的 URL。
- 304 資源未修改,拿快取的資源
301 和 302 都會在響應頭裡使用欄位 Location 指明後續要跳轉的 URI,最終的效果很相似,瀏覽器都會重定向到新的 URI。兩者的根本區別在於語義,一個是“永久”,一個是“臨時”。
比如,你的網站升級到了 HTTPS,原來的 HTTP 不打算用了,這就是“永久”的,所以要配置 301 跳轉,把所有的 HTTP 流量都切換到 HTTPS。
304 Not Modified 是一個比較有意思的狀態碼,它用於 If-Modified-Since 等條件請求,表示資源未修改,用於快取控制。它不具有通常的跳轉含義,但可以理解成“重定向已到快取的檔案”(即“快取重定向”)。
4xx 客戶端錯誤
- 400 bad request,通用的錯誤碼,請求報文存在語法錯誤。
- 403 forbidden,表示對請求資源的訪問被伺服器拒絕,伺服器經常訪問資源。
- 404 not found,表示在伺服器上沒有找到請求的資源。
- 405 Method Not Allowed,伺服器禁止使用該方法,客戶端可以通過options方法來檢視伺服器允許的訪問方法
5xx 服務的錯誤
- 500 internal sever error,表示伺服器端在執行請求時發生了錯誤。
- 502 Bad Gateway,伺服器自身是正常的,訪問的時候出了問題,具體啥錯誤我們不知道。
- 503 service unavailable,表明伺服器暫時處於超負載或正在停機維護,無法處理請求。
HTTP主要版本之間的差異
http0.9
http1.0
相比0.9版本,1.0版本增加了很多功能,例如:
- 傳輸的資料不再僅限於文字,影像、視訊、音訊都能傳輸
- 引入了 HTTP Header(頭部)的概念,讓 HTTP 處理請求和響應更加靈活
- 增加了響應狀態碼,標記可能的錯誤原因
- 增加了 HEAD、POST 等新方法
http1.1
http1.1是對 http1.0 的小幅度修改。但一個重要的區別是:它是一個“正式的標準”
- 明確了連線管理,允許持久連線,即TCP建立連線之後預設不關閉,可以被多個請求複用,不用宣告Connection: keep-alive
- 增加了 PUT、DELETE 等新的方法
- 增加了快取管理和控制,如增加了 E-tag,If-Unmodified-Since, If-Match, If-None-Match 等快取控制標頭來控制快取失效
- 支援斷點續傳,通過使用請求頭中的
Range
來實現。 - 允許響應資料分塊(chunked)傳輸,有利於傳輸大檔案
- 強制要求 請求頭帶上Host 頭,讓網際網路主機託管成為可能。
其中http1.1 版本是目前主流版本。
http2
隨著網際網路在飛速發展,目前http1.0 不符合現在的網路環境要求了,所以谷歌在1.0 的基礎上提出了SPDY 協議,優化了1.0。http/2 的制定充分考慮了現今網際網路的現狀:寬頻、移動、不安全,在高度相容 http/1.1 的同時在效能改善方面做了很大努力,
主要的特點有:
- 二進位制協議,不再是純文字,包括頭部欄位、body 的實體資料都是二進位制,並且統稱為"幀":頭資訊幀和資料幀
- 可發起多個請求,廢棄了 1.1 裡的管道
- 增加流、二進位制分幀的概念
- 多路複用TCP連線,在一個連線裡,客戶端和瀏覽器都可以同時傳送多個請求或回應,且不用按順序一一對應,這樣子解決了http隊頭阻塞的問題。
- 使用專用演算法(HPACK)壓縮頭部,減少資料傳輸量
- 允許伺服器主動向客戶端推送資料
http3
在http2 的基礎之上又進一步優化,出現了QUIC 協議,後面更名為http/3,HTTP/3 目前正式進入了標準化制訂階段。
TCP/IP 協議棧
tcp/ip 是一個有層次的協議棧,TCP/IP 協議總共有四層,就像搭積木一樣,每一層需要下層的支撐,同時又支撐著上層,任何一層被抽掉都可能會導致整個協議棧坍塌。每一層只做自己的事情,然後把結果給其他層,這樣的結構責任清晰,便於擴充套件。
第一層:連結層(資料鏈路層)負責在乙太網、WiFi 這樣的底層網路上傳送原始資料包,工作在網路卡這個層次,使用 MAC 地址來標記網路上的裝置,所以有時候也叫 MAC 層。
第二層:“網際層”或者“網路互連層”(internet layer)也叫網路層,IP 協議就處在這一層。因為 IP 協議定義了“IP 地址”的概念,所以就可以在“連結層”的基礎上,用 IP 地址取代 MAC 地址,把許許多多的區域網、廣域網連線成一個虛擬的巨大網路,在這個網路裡找裝置時只要把 IP 地址再“翻譯”成 MAC 地址就可以了。
第三層:“傳輸層”(transport layer),這個層次協議的職責是保證資料在 IP 地址標記的兩點之間“可靠”地傳輸,是 TCP 協議工作的層次,另外還有它的一個“小夥伴”UDP。
第四層叫“應用層”(application layer),由於下面的三層把基礎打得非常好,所以在這一層就“百花齊放”了,有各種面向具體應用的協議。例如 Telnet、SSH、FTP、SMTP 等等,當然還有我們的 HTTP。
TCP/IP工作方式
HTTP 協議的傳輸過程就是通過協議棧逐層向下,每一層都新增本層的專有資料,層層打包,然後通過下層傳送出去。
接收資料則是相反的操作,從下往上穿過協議棧,逐層拆包,每層去掉本層的專有頭,上層就會拿到自己的資料。
簡單的理解就是類似發快遞的過程,快遞員從你那拿到包裹,拿到地址,裝到他的車子,運送到站點,在分類打包,送去各個物流中轉站,到達之後,對方再依次拆包,拿到你寄送過去的物品。
TCP/IP四層與OSI七層模型
TCP/IP 發明於 1970 年代,當時除了它還有很多其他的網路協議,整個網路世界比較混亂。
這個時候國際標準組織(ISO)注意到了這種現象,就想要來個“大一統”。於是設計出了一個新的網路分層模型,想用這個新框架來統一既存的各種網路協議。TCP/IP 等協議已經在許多網路上實際執行,再推翻重來是不可能的。
所以,OSI 分層模型在釋出的時候就明確地表明是一個“參考”,不是強制標準
第一層:物理層,TCP/IP 裡無對應;
第二層:資料鏈路層,對應 TCP/IP 的連結層;
第三層:網路層,對應 TCP/IP 的網際層;
第四層:傳輸層,對應 TCP/IP 的傳輸層;
第五、六、七層:統一對應到 TCP/IP 的應用層。
HTTP 連線管理
目前主流版本http1.1 中的連線管理是會存在對頭阻塞的問題,所以效能是存在一點問題的,這個後面在說。
短連線
HTTP 協議最初(0.9/1.0)是個非常簡單的協議,通訊過程也採用了簡單的“請求 - 應答”方式。
它底層的資料傳輸基於 TCP/IP,每次傳送請求前需要先與伺服器建立連線,收到響應報文後會立即關閉連線。
因為客戶端與伺服器的整個連線過程很短暫,不會與伺服器保持長時間的連線狀態,所以就被稱為“短連線”(short-lived connections)。早期的 HTTP 協議也被稱為是“無連線”的協議。
短連線的缺點相當嚴重,因為在 TCP 協議裡,建立連線和關閉連線都是非常“昂貴”的操作。TCP 建立連線要有“三次握手”,傳送 3 個資料包;關閉連線是“四次揮手”,4 個資料包需要 。
長連線
針對短連線暴露出的缺點,HTTP 協議就提出了“長連線”的通訊方式,也叫“持久連線”。
其實解決辦法也很簡單,用的就是“成本均攤”的思路,既然 TCP 的連線和關閉非常耗時間,那麼就把這個時間成本由原來的一個“請求 - 應答”均攤到多個“請求 - 應答”上。
連線相關的頭部欄位
由於長連線對效能的改善效果非常顯著,所以在 HTTP/1.1 中的連線都會預設啟用長連線。不需要用什麼特殊的頭欄位指定,只要向伺服器傳送了第一次請求,後續的請求都會重複利用第一次開啟的 TCP 連線,也就是長連線,在這個連線上收發資料。
也可以在請求頭裡明確地要求使用長連線機制,使用的欄位是 Connection,值是“keep-alive”,如圖:
長連線缺點
因為 TCP 連線長時間不關閉,伺服器必須在記憶體裡儲存它的狀態,這就佔用了伺服器的資源。如果有大量的空閒長連線只連不發,就會很快耗盡伺服器的資源,導致伺服器無法為真正有需要的使用者提供服務。
所以,長連線也需要在恰當的時間關閉,不能永遠保持與伺服器的連線,這在客戶端或者伺服器都可以做到。
在客戶端,可以在請求頭裡加上“Connection: close”欄位,告訴伺服器:“這次通訊後就關閉連線”。伺服器看到這個欄位,就知道客戶端要主動關閉連線,於是在響應報文裡也加上這個欄位,傳送之後就呼叫 Socket API 關閉 TCP 連線。
伺服器端通常不會主動關閉連線,但也可以使用一些策略。拿 Nginx 來舉例,它有兩種方式:
- 使用keepalive_timeout指令,設定長連線的超時時間,如果在一段時間內連線上沒有任何資料收發就主動斷開連線,避免空閒連線佔用系統資源。
- 使用keepalive_requests指令,設定長連線上可傳送的最大請求次數。比如設定成 1000,那麼當 Nginx 在這個連線上處理了 1000 個請求後,也會主動斷開連線。
HTTP 快取控制
http 的快取是通過Cache-Control 欄位來控制
- max-age:是 HTTP 快取控制最常用的屬性,max-age=30”就是資源的有效時間,相當於告訴瀏覽器,“這個頁面只能快取 30 秒,之後就算是過期,不能用,這也就是經常說的強快取。
- no-store:不允許快取,用於某些變化非常頻繁的資料,例如秒殺頁面;
- no-cache:它的字面含義容易與 no-store 搞混,實際的意思並不是不允許快取,而是可以快取,但在使用之前必須要去伺服器驗證是否過期,是否有最新的版本,這也就是經常說的協商快取;
- must-revalidate:又是一個和 no-cache 相似的詞,它的意思是如果快取不過期就可以繼續使用,但過期瞭如果還想用就必須去伺服器驗證。
強快取
強快取兩個相關的欄位是Expires、Cache-Control,HTTP1.0版本使用的是Expires,HTTP1.1使用的是Cache-Control,當這兩個欄位同時在頭部欄位中出現時已Cache-Control 為準。
協商快取
HTTP 協議定義了一系列“If”開頭的“條件請求”欄位,專門用來檢查驗證資源是否過期,把兩個請求才能完成的工作合併在一個請求裡做。而且,驗證的責任也交給伺服器,瀏覽器只需等待驗證的結果。
我們最常用的欄位時if-Modified-Since和If-None-Match這兩個。
在第一次的響應報文裡提供Last-modified和ETag,然後第二次請求時就可以帶上快取裡的原值,驗證資源是否是最新的。如果資源沒有變,伺服器就回應一個“304 Not Modified”,表示快取依然有效,瀏覽器就可以更新一下有效期,然後放心大膽地使用快取了。
- if-Modified-Since:上一次響應頭裡的Last-modified
- If-None-Match:上一次響應頭裡的ETag
ETag
ETag是伺服器根據當前檔案的內容,對檔案生成唯一的標識,比如MD5演算法,只要裡面的內容有改動,這個值就會修改,伺服器通過把響應頭把該欄位傳送給瀏覽器。
瀏覽器接受到ETag值,會在下次請求的時候,將這個值作為If-None-Match這個欄位的內容,發給伺服器。
伺服器接收到If-None-Match後,會跟伺服器上該資源的ETag進行比對
- 如果兩者一樣的話,直接返回304,告訴瀏覽器直接使用快取
- 如果不一樣的話,說明內容更新了,返回新的資源,跟常規的HTTP請求響應的流程一樣
Etag 一般由web 應用伺服器生成,根據修改時間、內容大小等生成一個字串,可以理解為通過某種演算法生成 如MD5; 比如
nginx 中 etag 由響應頭的 Last-Modified 與 Content-Length 表示為十六進位制組合而成,不同的web 伺服器採用的演算法應該不一樣;
etag 還分強、弱,通常都是採用弱etag,強etag 耗時間,消耗資源,具體的可以查閱相關資料;