自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

HTTP 中的 ETag 是如何生成的?

網(wǎng)絡(luò) 網(wǎng)絡(luò)管理
本文阿寶哥將介紹如何利用 ETag 和 If-None-Match 來實(shí)現(xiàn)緩存控制。此外,阿寶哥還將介紹 HTTP 中的 ETag 是如何生成的。不過在此之前,我們得先來簡單介紹一下 ETag。

[[401507]]

深入理解瀏覽器的緩存機(jī)制 這篇文章詳細(xì)介紹了瀏覽器緩存相關(guān)的內(nèi)容,本文阿寶哥將介紹如何利用 ETag 和 If-None-Match 來實(shí)現(xiàn)緩存控制。此外,阿寶哥還將介紹 HTTP 中的 ETag 是如何生成的。不過在此之前,我們得先來簡單介紹一下 ETag。

一、ETag 簡介

1.1 ETag 是什么

ETag(Entity Tag)是萬維網(wǎng)協(xié)議 HTTP 的一部分。它是 HTTP 協(xié)議提供的若干機(jī)制中的一種 Web 緩存驗(yàn)證機(jī)制,并且允許客戶端進(jìn)行緩存協(xié)商。這使得緩存變得更加高效,而且節(jié)省帶寬。如果資源的內(nèi)容沒有發(fā)生改變,Web 服務(wù)器就不需要發(fā)送一個(gè)完整的響應(yīng)。

1.2 ETag 的作用

ETag 是一個(gè)不透明的標(biāo)識符,由 Web 服務(wù)器根據(jù) URL 上的資源的特定版本而指定。如果 URL 上的資源內(nèi)容改變,一個(gè)新的不一樣的 ETag 就會(huì)被生成。ETag 可以看成是資源的指紋,它們能夠被快速地比較,以確定兩個(gè)版本的資源是否相同。

需要注意的是 ETag 的比較只對同一個(gè) URL 有意義 —— 不同 URL 上資源的 ETag 值可能相同也可能不同。

1.3 ETag 的語法

  1. ETag: W/"<etag_value>" 
  2. ETag: "<etag_value>" 

W/(可選):'W/'(大小寫敏感) 表示使用弱驗(yàn)證器。弱驗(yàn)證器很容易生成,但不利于比較。強(qiáng)驗(yàn)證器是比較的理想選擇,但很難有效地生成。相同資源的兩個(gè)弱 Etag 值可能語義等同,但不是每個(gè)字節(jié)都相同。

"<etag_value>":實(shí)體標(biāo)簽唯一地表示所請求的資源。它們是位于雙引號之間的 ASCII 字符串(如 “2c-1799c10ab70” )。沒有明確指定生成 ETag 值的方法。通常是使用內(nèi)容的散列、最后修改時(shí)間戳的哈希值或簡單地使用版本號。比如,MDN 使用 wiki 內(nèi)容的十六進(jìn)制數(shù)字的哈希值。

1.4 ETag 的使用

在大多數(shù)場景下,當(dāng)一個(gè) URL 被請求,Web 服務(wù)器會(huì)返回資源和其相應(yīng)的 ETag 值,它會(huì)被放置在 HTTP 響應(yīng)頭的 ETag 字段中:

  1. HTTP/1.1 200 OK 
  2. Content-Length: 44 
  3. Cache-Control: max-age=10 
  4. Content-Type: application/javascript; charset=utf-8 
  5. ETag: W/"2c-1799c10ab70" 

然后,客戶端可以決定是否緩存這個(gè)資源和它的 ETag。以后,如果客戶端想再次請求相同的 URL,將會(huì)發(fā)送一個(gè)包含已保存的 ETag 和 If-None-Match 字段的請求。

  1. GET /index.js HTTP/1.1 
  2. Host: localhost:3000 
  3. Connection: keep-alive 
  4. If-None-Match: W/"2c-1799c10ab70" 

客戶端請求之后,服務(wù)器可能會(huì)比較客戶端的 ETag 和當(dāng)前版本資源的 ETag。如果 ETag 值匹配,這就意味著資源沒有改變,服務(wù)器便會(huì)發(fā)送回一個(gè)極短的響應(yīng),包含 HTTP “304 未修改” 的狀態(tài)。304 狀態(tài)碼告訴客戶端,它的緩存版本是最新的,可以直接使用它。

  1. HTTP/1.1 304 Not Modified 
  2. Cache-Control: max-age=10 
  3. ETag: W/"2c-1799c10ab70" 
  4. Connection: keep-alive 

二、ETag 實(shí)戰(zhàn)

2.1 創(chuàng)建 Koa 服務(wù)器

了解完 ETag 相關(guān)知識后,阿寶哥將基于 koa、koa-conditional-get、koa-etag 和 koa-static 這些庫來介紹一下,在實(shí)際項(xiàng)目中如何利用 ETag 響應(yīng)頭和 If-None-Match請求頭實(shí)現(xiàn)資源的緩存控制。

  1. // server.js 
  2. const Koa = require("koa"); 
  3. const path = require("path"); 
  4. const serve = require("koa-static"); 
  5. const etag = require("koa-etag"); 
  6. const conditional = require("koa-conditional-get"); 
  7.  
  8. const app = new Koa(); 
  9.  
  10. app.use(conditional()); // 使用條件請求中間件 
  11. app.use(etag()); // 使用etag中間件 
  12. app.use( // 使用靜態(tài)資源中間件 
  13.   serve(path.join(__dirname, "/public"), { 
  14.     maxage: 10 * 1000, // 設(shè)置緩存存儲的最大周期,單位為秒 
  15.   }) 
  16. ); 
  17.  
  18. app.listen(3000, () => { 
  19.   console.log("app starting at port 3000"); 
  20. }); 

在以上代碼中,我們使用了 koa-static 中間件來處理靜態(tài)資源,這些資源被保存在 public 目錄下。在該目錄下,阿寶哥創(chuàng)建了 index.html 和 index.js 兩個(gè)資源文件,文件中的內(nèi)容分別如下所示:

2.1.1 public/index.html

  1. <!DOCTYPE html> 
  2. <html lang="zh-cn"
  3. <head> 
  4.     <meta charset="UTF-8"
  5.     <meta http-equiv="X-UA-Compatible" content="IE=edge"
  6.     <meta name="viewport" content="width=device-width, initial-scale=1.0"
  7.     <title>ETag 使用示例</title> 
  8.     <script src="/index.js"></script> 
  9. </head> 
  10. <body> 
  11.     <h3>ETag 使用示例</h3> 
  12. </body> 
  13. </html> 

 2.1.2 public/index.js

  1. console.log("大家好,我是阿寶哥"); 

在啟動(dòng)完服務(wù)器之后,我們打開 Chrome 開發(fā)者工具并切換到 Network 標(biāo)簽欄,然后在瀏覽器地址欄輸入 http://localhost:3000/ 地址,接著多次訪問該地址(地址欄多次回車)。下圖是阿寶哥多次訪問的結(jié)果:

2.2 ETag 和 If-None-Match

下面阿寶哥將以 index.js 為例,來分析上圖中與之對應(yīng)的 HTTP 報(bào)文。對于 index.html 文件,感興趣的小伙伴可以自行分析一下。接下來我們先來分析首次請求 index.js 文件的報(bào)文:

2.2.1 首次請求 — 請求報(bào)文

  1. GET /index.js HTTP/1.1 
  2. Host: localhost:3000 
  3. Connection: keep-alive 
  4. Pragma: no-cache 
  5. Cache-Control: no-cache 
  6. ... 

2.2.2 首次請求 — 響應(yīng)報(bào)文

  1. HTTP/1.1 200 OK 
  2. Content-Length: 44 
  3. Cache-Control: max-age=10 
  4. ETag: W/"2c-1799c10ab70" 
  5. ... 

在使用了 koa-static 和 koa-etag 中間件之后,index.js 文件首次請求的響應(yīng)報(bào)文中會(huì)包含 Cache-Control 和 ETag 的字段信息。

  • Cache-Control 描述的是一個(gè)相對時(shí)間,在進(jìn)行緩存命中的時(shí)候,都是利用客戶端時(shí)間進(jìn)行判斷,所以相比較 Expires,Cache-Control 的緩存管理更有效,安全一些。

2.2.3 10s內(nèi) — 請求報(bào)文

  1. GET /index.js HTTP/1.1 
  2. Host: localhost:3000 
  3. Connection: keep-alive 
  4. Pragma: no-cache 
  5. Cache-Control: no-cache 
  6. ... 

2.2.4 10s內(nèi) — 響應(yīng)信息(General)

  1. Request URL: http://localhost:3000/index.js 
  2. Request Method: GET 
  3. Status Code: 200 OK (from memory cache) 
  4. Remote Address: [::1]:3000 
  5. Referrer Policy: strict-origin-when-cross-origin 

2.2.5 10s內(nèi) — 響應(yīng)信息(Response Headers)

  1. Cache-Control: max-age=10 
  2. Connection: keep-alive 
  3. Content-Length: 44 
  4. ETag: W/"2c-1799c10ab70" 

由于我們設(shè)置了 index.js 資源文件的最大緩存時(shí)間為 10s,所以在 10s 內(nèi)瀏覽器會(huì)直接從緩存中讀取文件的內(nèi)容。需要注意的是,此時(shí)的狀態(tài)碼為:Status Code: 200 OK (from memory cache)。

2.2.6 10s后 — 請求報(bào)文

  1. GET /index.js HTTP/1.1 
  2. Host: localhost:3000 
  3. Connection: keep-alive 
  4. If-None-Match: W/"2c-1799c10ab70" 
  5. Referer: http://localhost:3000/ 
  6. ... 

因?yàn)?10s 之后,緩存已經(jīng)過期了,而且在 index.js 文件首次請求的響應(yīng)報(bào)文中也返回了 ETag 字段。所以此時(shí)瀏覽器會(huì)發(fā)起 If-None-Match 條件請求。這類請求可以用來驗(yàn)證緩存的有效性,省去不必要的控制手段。

2.2.7 10s后 — 響應(yīng)報(bào)文

  1. HTTP/1.1 304 Not Modified 
  2. Cache-Control: max-age=10 
  3. ETag: W/"2c-1799c10ab70" 
  4. Connection: keep-alive 
  5. ... 

因?yàn)槲募膬?nèi)容未發(fā)生改變,所以 10s 后的響應(yīng)報(bào)文的狀態(tài)碼為 304 Not Modified。此外,響應(yīng)報(bào)文中也返回了 ETag 字段??吹竭@里,有一些小伙伴可能會(huì)有疑惑 —— ETag 到底是如何生成的?接下來,阿寶哥將帶大家一起來揭開 koa-etag 中間件背后的秘密。

三、如何生成 ETag

在前面的示例中,我們使用了 koa-etag 中間件來實(shí)現(xiàn)資源的緩存控制。其實(shí)該中間件的實(shí)現(xiàn)并不復(fù)雜,具體如下所示:

  1. // https://github.com/koajs/etag/blob/master/index.js 
  2. const calculate = require('etag'); 
  3. // 省略部分代碼 
  4.  
  5. module.exports = function etag (options) { 
  6.   return async function etag (ctx, next) { 
  7.     await next() 
  8.     const entity = await getResponseEntity(ctx) 
  9.     setEtag(ctx, entity, options) 
  10.   } 

由以上代碼可知,在 koa-etag 中間件內(nèi)部會(huì)先通過 getResponseEntity 函數(shù)來獲取響應(yīng)實(shí)體對象,然后再調(diào)用 setETag 函數(shù)來生成 ETag。而 setETag 函數(shù)的實(shí)現(xiàn)很簡單,在 setETag 函數(shù)內(nèi)部,會(huì)通過 etag 這個(gè)第三方庫來生成 ETag。

  1. // https://github.com/koajs/etag/blob/master/index.js 
  2. function setEtag (ctx, entity, options) { 
  3.   if (!entity) return 
  4.   ctx.response.etag = calculate(entity, options) 

etag 這個(gè)庫對外提供了一個(gè) etag 函數(shù)來創(chuàng)建 ETag,該函數(shù)的簽名如下:

  1. etag(entity, [options]) 

entity:用于生成 ETag 的實(shí)體,類型支持 Strings,Buffers 和 fs.Stats。除了 fs.Stats 對象之外,默認(rèn)將生成 strong ETag。

options:配置對象,支持通過 options.weak 屬性來配置生成 weak ETag。

了解完 etag 函數(shù)的參數(shù)之后,我們來看一下該函數(shù)的具體實(shí)現(xiàn):

  1. function etag (entity, options) { 
  2.   if (entity == null) { 
  3.     throw new TypeError('argument entity is required'
  4.   } 
  5.  
  6.   // 支持fs.Stats對象 
  7.   // isstats 函數(shù)的判斷規(guī)則:當(dāng)前對象是否包含ctime、mtime、ino和size這些屬性 
  8.   var isStats = isstats(entity) 
  9.   var weak = options && typeof options.weak === 'boolean' 
  10.     ? options.weak 
  11.     : isStats 
  12.  
  13.   // 參數(shù)校驗(yàn) 
  14.   if (!isStats && typeof entity !== 'string' && !Buffer.isBuffer(entity)) { 
  15.     throw new TypeError('argument entity must be string, Buffer, or fs.Stats'
  16.   } 
  17.  
  18.   // 生成ETag標(biāo)簽 
  19.   var tag = isStats 
  20.     ? stattag(entity) // 處理fs.Stats對象 
  21.     : entitytag(entity) 
  22.  
  23.   return weak 
  24.     ? 'W/' + tag 
  25.     : tag 

在 etag 函數(shù)內(nèi)部會(huì)根據(jù) entity 的類型,執(zhí)行不同的生成邏輯。如果 entity 是 fs.Stats 對象,則會(huì)調(diào)用 stattag 函數(shù)來創(chuàng)建 ETag。

  1. function stattag (stat) { 
  2.   // mtime:Modified Time,是在寫入文件時(shí)隨文件內(nèi)容的更改而更改,是指文件內(nèi)容最后一次被修改的時(shí)間。 
  3.   var mtime = stat.mtime.getTime().toString(16) 
  4.   var size = stat.size.toString(16) 
  5.  
  6.   return '"' + size + '-' + mtime + '"' 

而如果 entity 參數(shù)非 fs.Stats 對象,則會(huì)調(diào)用 entitytag 函數(shù)來生成 ETag。其中 entitytag 函數(shù)的具體實(shí)現(xiàn)如下:

  1. function entitytag (entity) { 
  2.   if (entity.length === 0) { 
  3.     return '"0-2jmj7l5rSw0yVb/vlWAYkK/YBwk"' 
  4.   } 
  5.  
  6.   // 計(jì)算實(shí)體對象的哈希值 
  7.   var hash = crypto 
  8.     .createHash('sha1'
  9.     .update(entity, 'utf8'
  10.     .digest('base64'
  11.     .substring(0, 27) 
  12.  
  13.   // 計(jì)算實(shí)體對象的長度 
  14.   var len = typeof entity === 'string' 
  15.     ? Buffer.byteLength(entity, 'utf8'
  16.     : entity.length 
  17.  
  18.   return '"' + len.toString(16) + '-' + hash + '"' 

對于非 fs.Stats 對象來說,在 entitytag 函數(shù)內(nèi)部會(huì)使用 sha1 消息摘要算法來生成 hash 值并以 base64 格式輸出,而實(shí)際的生成的 hash 值會(huì)取前 27 個(gè)字符。此外,由以上代碼可知,最終的 ETag 將由實(shí)體的長度和哈希值兩部分組成。

需要注意的是,生成 ETag 的算法并不是固定的, 通常是使用內(nèi)容的散列、最后修改時(shí)間戳的哈希值或簡單地使用版本號。

四、ETag vs Last-Modified

其實(shí)除了 ETag 字段之外,大多數(shù)情況下,響應(yīng)頭中還會(huì)包含 Last-Modified 字段。它們之間的區(qū)別如下:

精確度上,Etag 要優(yōu)于 Last-Modified。Last-Modified 的時(shí)間單位是秒,如果某個(gè)文件在 1 秒內(nèi)被改變多次,那么它們的 Last-Modified 并沒有體現(xiàn)出來修改,但是 Etag 每次都會(huì)改變,從而確保了精度;此外,如果是負(fù)載均衡的服務(wù)器,各個(gè)服務(wù)器生成的 Last-Modified 也有可能不一致。

性能上,Etag 要遜于 Last-Modified,畢竟 Last-Modified 只需要記錄時(shí)間,而 ETag 需要服務(wù)器通過消息摘要算法來計(jì)算出一個(gè)hash 值。

優(yōu)先級上,在資源新鮮度校驗(yàn)時(shí),服務(wù)器會(huì)優(yōu)先考慮 Etag。即如果條件請求的請求頭同時(shí)攜帶 If-Modified-Since 和 If-None-Match 字段,則會(huì)優(yōu)先判斷資源的 ETag 值是否發(fā)生變化。

五、總結(jié)

本文阿寶哥首先介紹了 ETag 的相關(guān)基礎(chǔ)知識,然后以 Koa 為例詳細(xì)介紹了 ETag 和 If-None-Match 是如何實(shí)現(xiàn)緩存控制的。此外,阿寶哥還分析了 koa-etag 中間件內(nèi)部依賴的 etag 第三方庫是如何為指定的實(shí)體生成 ETag 對象。最后,阿寶哥列舉了 ETag 與 Last-Modified 之間的主要區(qū)別。

如果你還想進(jìn)一步了解瀏覽器的緩存機(jī)制,你可以閱讀 深入理解瀏覽器的緩存機(jī)制 這篇文章。在后續(xù)的文章中,阿寶哥將介紹如何實(shí)現(xiàn)資源的新鮮度檢測,感興趣的小伙伴不要錯(cuò)過喲。

 

責(zé)任編輯:姜華 來源: 全棧修仙之路
相關(guān)推薦

2021-07-23 15:55:31

HTTPETag前端

2023-03-06 07:25:09

http響應(yīng)頭ETag

2021-07-27 14:50:15

axiosHTTP前端

2019-07-02 08:24:07

HTTPHTTPSTCP

2019-12-13 09:14:35

HTTP2協(xié)議

2010-05-07 12:20:38

負(fù)載均衡etag

2023-09-19 22:41:30

控制器HTTP

2019-04-08 15:11:12

HTTP協(xié)議Web

2018-03-05 19:20:49

LinuxWordPressHTTP

2017-11-17 09:13:31

Java注解

2022-09-16 00:11:45

PyTorch神經(jīng)網(wǎng)絡(luò)存儲

2021-01-18 05:13:04

TomcatHttp

2024-09-30 08:43:33

HttpgolangTimeout

2022-05-18 08:00:00

JavaScriptFetch數(shù)據(jù)

2020-03-17 23:08:32

數(shù)據(jù)Elasticsear存儲

2024-12-16 17:02:58

MySQLInnoDB數(shù)據(jù)庫

2012-08-27 09:10:05

JVMJava

2021-05-12 08:20:53

開發(fā)

2009-08-04 13:31:35

C#自定義事件

2021-08-06 09:21:26

Linux內(nèi)核 Coredump
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號