HTTP 協議(包體的傳輸方式&緩存的工做原理)

HTTP 協議(包體的傳輸方式&緩存的工做原理)

這篇文章主要了解一下 HTTP 協議中定長包體傳輸的格式和不定長包體傳輸的格式,而後簡單介紹一下 HTTP 協議中緩存的工做原理和應用場景。瀏覽器

1.HTTP 包體

1.1 HTTP 包體格式

請求或者響應均可以攜帶包體,基於 ABNF 描述的格式以下:緩存

#message-body = *OCTET:二進制字節流
HTTP-message = start-line *(header-field CRLF) CRLF [message-body]

1.2 不能攜帶包體的請求或響應

  • HEAD 方法請求時對應的響應不能攜帶包體。
  • 1xx204304 狀態碼對應的響應。
  • CONNECT 方法對應的 2xx 響應。

1.3 發送定長包體格式

在發送 HTTP 消息時已經可以肯定包體的所有長度,格式以下:服務器

#使用 Content-Length 頭部明確指明包體長度
Content-Length = 1*DIGIT
Tips: 1*DIGIT 表示用 1 個 十進制(不是十六進制)數表示包體中的字節個數,必須與實際傳輸的包體長度一致,它的優勢是接收端處理簡單。

1.4 發送不定長包體格式

發送 HTTP 消息時不能肯定包體的所有長度,須要使用 Transfer-Encoding 頭部指明使用 Chunk 傳輸方式,含有 Transfer-Encoding 頭部後 Content-Length 頭部會被忽略。不定長包體優勢以下:網絡

  • 基於長鏈接持續推送動態內容。
  • 壓縮體積較大的包體時,沒必要徹底壓縮完(計算出頭部)再發送,能夠邊發送邊壓縮。
  • 傳遞必須在包體傳輸完才能計算出的 Trailer 頭部。
transfer-coding = "chunked" / "compress" / "deflate" / "gzip" / transfer-extension

Chunked transfer encoding 分塊傳輸編碼:Transfer-Encoding:chunked

chunked-body = *chunk
                last-chunk
                trailer-part
                CRLF
                
chunk = chunk-size[chunk-ext] CRLF chunk-data CRLF

#chunk-size-1*HEXDIG:注意這裏是十六進制

chunk-data-1*OCTET

last-chunk = 1*("0")[chunk-ext] CRLF

trailer-part = *(header-field CRLF)

2.經常使用 HTTP 緩存應用場景

以下圖所示,以百度首頁爲例,展現了 HTTP 緩存場景:
curl

Tips: disk cache 表示磁盤緩存,下次訪問的時候不須要下載,能夠直接去磁盤獲取, memory cache 表示緩存存在內存中,當瀏覽器退出進程時,內存中的數據會被清空。

3.HTTP 緩存實現示意圖

Tips:以 HTTP 請求中部分信息(如 schema、path、host)構成的字典, HTTP 響應消息構成的 LRU 鏈表。

4.判斷緩存是否過時

4.1 freshness_lifetime

判斷緩存是夠過時可使用以下格式的公式計算:ide

#freshness_lifetime:按優先級,取如下響應頭部的值

#s-maxage > max-age > Expires > 預估過時時間

# Cache-Control:s-maxage=3600
# Cache-control:max-age=86400
# Expires:Fri,03 May 2019 03:15:20 GMT
# Expires-HTTP-date,指明緩存的絕對過時時間
response_is_fresh = (freshness_lifetime > current_age)

freshness_lifetime 以下圖所示:
編碼

下面給出各類緩存時間請求統計佔比狀況:
url

Tips:緩存的預估時間計算參照 RFC7234文檔中 (DownloadTime-LastModified)*10%

4.2 current_age

  • Age 頭部

Age 頭部表示來自原服務器發出響應(或者驗證過時緩存),到使用緩存的響應發出時通過的秒數,對於代理服務器管理的共享緩存,客戶端能夠根據 Age 頭部判斷緩存時間,格式以下:spa

Age = delta-seconds
  • current_age 計算公式以下:
:current_age = corrected_initial_age + resident_time

4.3 模擬瀏覽器緩存工做原理

步驟1:從瀏覽器複製一個緩存相關的請求命令:
以下圖所示,能夠在百度首頁顯示有緩存的資源右鍵複製出相關的 HTTP 請求的 curl 命令:

命令以下:代理

curl 'https://dss0.bdstatic.com/5aV1bjqh_Q23odCf/static/superman/js/min_notice-816c20c940.js' -H 'Referer: https://www.baidu.com/'  -H 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36' --compressed -I
Tips:命令最後增長一個 -I 表示返回輸出頭部內容。

請求結果以下圖:

步驟2:使用 If-None-Match 條件判斷請求內容是否過時:

curl 'https://dss0.bdstatic.com/5aV1bjqh_Q23odCf/static/superman/js/min_notice-816c20c940.js' -H 'Referer: https://www.baidu.com/'  -H 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36' --compressed  -H 'If-None-Match' -I

以下圖所示:

Tips: HTTP 請求時帶上 If-None-Match 能夠判斷 Etag 指紋對應緩存內的容是否過時,若返回 304 表示緩存能夠繼續使用。

5.私有緩存和共享緩存

  • 私有緩存:僅供一個用戶使用的緩存,一般只存在於瀏覽器這樣的客戶端。
  • 共享緩存:能夠供多個用戶的緩存,存在於網絡中負責轉發消息的代理服務器(對熱點資源常使用共享緩存,以減輕原服務器的壓力,並提高網絡效率)
  • Authentication 響應不可被代理服務器緩存

6. Cache-Control 頭部

Cache-ControlABNF 中的秒數以下:

Cache-Control = 1#cache-directive 

cache-directive = token ["=" (token / quoted-string)]

delta-seconds = 1*DIGIT
Tips: RFC規範中的要求是至少能支持到 2147483648(2^31)

6.1 請求中頭部的 Cache-Control

請求中 Cache-Control 的取值有 max-agemax-stalemin-freshno-cacheno-storeno-tansformonly-if-cached,它們的含義以下:

  • max-age:告訴服務器,客戶端不會接受 Age 超出 max-age 秒的緩存。
  • max-stale:告訴服務器,即便緩存再也不新鮮,但陳舊描述沒有超出 max-stale 時,客戶端仍打算使用,若 max-stale 後沒有值,則表示不管過時多久客戶端均可使用。
  • min-fresh:告訴服務器,Age至少通過 min-fresh 秒後緩存纔可使用。
  • no-cache:告訴服務器,不能直接使用已有緩存做爲響應返回,除非帶着緩存條件到上游服務端獲得 304 驗證返回碼纔可以使用現有緩存。
  • no-store:告訴個代理服務器不要對請求的響應緩存(實際有很多不遵循該規定的代理服務器)。
  • no-tansform:告訴代理服務器不要修改消息包體的內容。
  • noly-if-cached:告訴服務器僅能返回緩存的響應,不然若沒有緩存則返回 504 錯誤碼。

6.2 響應中頭部的 Cache-Control

響應中 Cache-Control 的取值有 max-ages-maxagemust-revalidateproxy-revalidateno-cacheno-storeno-transformpublicprivate,它們的含義以下:

  • must-revalidate:告訴客戶端一旦緩存過時,必須向服務器驗證後纔可使用。
  • proxy-revalidate:與 must-revalidate 相似,但它僅對代理服務器的共享緩存有效。
  • no-cache:告訴客戶端不能直接使用緩存的響應,使用前必須在源服務器驗證獲得 304 返回碼。若是 no-cache 後指定頭部,則客戶端的後續請求及響應中不含有這些頭則可直接使用緩存。
  • max-age:告訴客戶端緩存 Age 超出 max-age 秒後則緩存過時。
  • s-maxage:與 max-age 類似,但僅針對共享緩存,且優先級高於 max-ageExpires
  • public:表示不管私有緩存或者共享緩存,皆可將該響應緩存。
  • private:表示該響應不能被代理服務器做爲共享緩存使用,若 private 後指定頭部,則在告訴代理服務器不能緩存指定的頭部,但可緩存其餘部分。
  • no-store:告訴全部下游節點不能對響應進行緩存。
  • no-transform:告訴代理服務器不能修改消息包體的內容。

7.什麼樣的 HTTP 響應會被緩存

  • 請求方法能夠被緩存理解(不只僅 GET 方法)
  • 響應碼能夠被緩存理解(40四、206也能夠被緩存)
  • 響應與請求的頭部沒有指明 no-store
  • 響應中至少應含有如下頭部中的 1 個或者多個:
Expires、max-age、s-maxage、public
#當響應中沒有明確指示過時時間的頭部時,若是響應碼很是明確,也能夠緩存
  • 若是緩存在代理服務器上
不含有 private
不含有 Authorization

8.使用緩存做爲當前請求響應的條件

  • URI 做爲主要的緩存關鍵字,當一個 URI 同時對應多份緩存時,選擇日期最近的緩存。例如 Nginx 中默認的緩存關鍵字:proxy_cache_key
$scheme$proxy_host$request_uri;
  • 緩存中的響應容許當前請求的方法使用緩存
  • 緩存中的響應 Vary 頭部指定的頭部必須與請求中的頭部相匹配:
Vary = "*" / 1#field-name

vary:*意味着必定匹配失敗
  • 當前請求以及緩存中的響應都不包含 no-cache 頭部(Pragma:no-cache或者Cache-Control:no-cache)
  • 緩存中的響應必須是如下三者之一:
#新鮮的
#緩存中的響應頭部明確告知可使用過時的響應(如 Cache-Control:max-stale=60)
#使用條件請求去服務器端驗證請求是否過時,獲得 304 響應

掃碼關注愛因詩賢
在這裏插入圖片描述

相關文章
相關標籤/搜索