成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

HTTP 中的 ETag 是如何生成的?

開發 前端
本文將介紹如何利用 ETag 和 If-None-Match 來實現緩存控制。此外,還將介紹 HTTP 中的 ETag 是如何生成的。不過在此之前,我們得先來簡單介紹一下 ETag。

 [[412708]]

本文將介紹如何利用 ETag 和 If-None-Match 來實現緩存控制。此外,還將介紹 HTTP 中的 ETag 是如何生成的。不過在此之前,我們得先來簡單介紹一下 ETag。

一、ETag 簡介

1.1 ETag 是什么

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

1.2 ETag 的作用

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

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

1.3 ETag 的語法 

  1. ETag: W/"<etag_value> 
  2. ETag: "<etag_value>
  •  W/(可選):'W/'(大小寫敏感) 表示使用弱驗證器。弱驗證器很容易生成,但不利于比較。強驗證器是比較的理想選擇,但很難有效地生成。相同資源的兩個弱 Etag 值可能語義等同,但不是每個字節都相同。
  •  "<etag_value>":實體標簽唯一地表示所請求的資源。它們是位于雙引號之間的 ASCII 字符串(如 “2c-1799c10ab70” )。沒有明確指定生成 ETag 值的方法。通常是使用內容的散列、最后修改時間戳的哈希值或簡單地使用版本號。比如,MDN 使用 wiki 內容的十六進制數字的哈希值。

1.4 ETag 的使用

在大多數場景下,當一個 URL 被請求,Web 服務器會返回資源和其相應的 ETag 值,它會被放置在 HTTP 響應頭的 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" 

然后,客戶端可以決定是否緩存這個資源和它的 ETag。以后,如果客戶端想再次請求相同的 URL,將會發送一個包含已保存的 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" 

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

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

二、ETag 實戰

2.1 創建 Koa 服務器

了解完 ETag 相關知識后,將基于 koa、koa-conditional-get、koa-etag 和 koa-static 這些庫來介紹一下,在實際項目中如何利用 ETag 響應頭和 If-None-Match 請求頭實現資源的緩存控制。 

  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. const app = new Koa();  
  8. app.use(conditional()); // 使用條件請求中間件  
  9. app.use(etag()); // 使用etag中間件  
  10. app.use( // 使用靜態資源中間件  
  11.   serve(path.join(__dirname, "/public"), {  
  12.     maxage: 10 * 1000, // 設置緩存存儲的最大周期,單位為秒  
  13.   })  
  14. );  
  15. app.listen(3000, () => {  
  16.   console.log("app starting at port 3000");  
  17. }); 

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

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

console.log("大家好,");

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

2.2 ETag 和 If-None-Match

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

2.2.1 首次請求 — 請求報文 

  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 首次請求 — 響應報文 

  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 文件首次請求的響應報文中會包含 Cache-Control 和 ETag 的字段信息。

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

2.2.3 10s內 — 請求報文 

  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內 — 響應信息(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內 — 響應信息(Response Headers) 

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

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

2.2.6 10s后 — 請求報文 

  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. ... 

因為 10s 之后,緩存已經過期了,而且在 index.js 文件首次請求的響應報文中也返回了 ETag 字段。所以此時瀏覽器會發起 If-None-Match 條件請求。這類請求可以用來驗證緩存的有效性,省去不必要的控制手段。

2.2.7 10s后 — 響應報文 

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

因為文件的內容未發生改變,所以 10s 后的響應報文的狀態碼為 304 Not Modified。此外,響應報文中也返回了 ETag 字段。看到這里,有一些小伙伴可能會有疑惑 —— ETag 到底是如何生成的?接下來,將帶大家一起來揭開 koa-etag 中間件背后的秘密。

三、如何生成 ETag

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

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

由以上代碼可知,在 koa-etag 中間件內部會先通過 getResponseEntity 函數來獲取響應實體對象,然后再調用 setETag 函數來生成 ETag。而 setETag 函數的實現很簡單,在 setETag 函數內部,會通過 etag 這個第三方庫來生成 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 這個庫對外提供了一個 etag 函數來創建 ETag,該函數的簽名如下: 

  1. etag(entity, [options]) 
  •  entity:用于生成 ETag 的實體,類型支持 Strings,Buffers 和 fs.Stats。除了 fs.Stats 對象之外,默認將生成 strong ETag。
  •  options:配置對象,支持通過 options.weak 屬性來配置生成 weak ETag。

了解完 etag 函數的參數之后,我們來看一下該函數的具體實現: 

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

在 etag 函數內部會根據 entity 的類型,執行不同的生成邏輯。如果 entity 是 fs.Stats 對象,則會調用 stattag 函數來創建 ETag。 

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

而如果 entity 參數非 fs.Stats 對象,則會調用 entitytag 函數來生成 ETag。其中 entitytag 函數的具體實現如下: 

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

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

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

四、ETag vs Last-Modified

其實除了 ETag 字段之外,大多數情況下,響應頭中還會包含 Last-Modified 字段。它們之間的區別如下:

  •  精確度上,Etag 要優于 Last-Modified。Last-Modified 的時間單位是秒,如果某個文件在 1 秒內被改變多次,那么它們的 Last-Modified 并沒有體現出來修改,但是 Etag 每次都會改變,從而確保了精度;此外,如果是負載均衡的服務器,各個服務器生成的 Last-Modified 也有可能不一致。
  •  性能上,Etag 要遜于 Last-Modified,畢竟 Last-Modified 只需要記錄時間,而 ETag 需要服務器通過消息摘要算法來計算出一個hash 值。
  •  優先級上,在資源新鮮度校驗時,服務器會優先考慮 Etag。即如果條件請求的請求頭同時攜帶 If-Modified-Since 和 If-None-Match 字段,則會優先判斷資源的 ETag 值是否發生變化。

五、總結

本文首先介紹了 ETag 的相關基礎知識,然后以 Koa 為例詳細介紹了 ETag 和 If-None-Match 是如何實現緩存控制的。此外,還分析了 koa-etag 中間件內部依賴的 etag 第三方庫是如何為指定的實體生成 ETag 對象。最后,列舉了 ETag 與 Last-Modified 之間的主要區別。 

 

責任編輯:龐桂玉 來源: 前端大全
相關推薦

2021-05-26 05:18:51

HTTP ETag Entity Tag

2023-03-06 07:25:09

http響應頭ETag

2019-12-13 09:14:35

HTTP2協議

2021-07-27 14:50:15

axiosHTTP前端

2019-04-08 15:11:12

HTTP協議Web

2019-07-02 08:24:07

HTTPHTTPSTCP

2018-03-05 19:20:49

LinuxWordPressHTTP

2010-05-07 12:20:38

負載均衡etag

2023-09-19 22:41:30

控制器HTTP

2017-11-17 09:13:31

Java注解

2022-09-16 00:11:45

PyTorch神經網絡存儲

2021-01-18 05:13:04

TomcatHttp

2024-09-30 08:43:33

HttpgolangTimeout

2011-11-03 16:32:57

Dart

2022-04-14 09:01:39

React源碼Flow

2009-08-04 13:31:35

C#自定義事件

2021-08-06 09:21:26

Linux內核 Coredump

2021-05-12 08:20:53

開發

2020-03-17 23:08:32

數據Elasticsear存儲

2022-05-18 08:00:00

JavaScriptFetch數據
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 亚洲精品黄色 | 自拍第一页 | 亚洲成人日韩 | 91精品国产乱码久久久久久久 | 日韩一区二区三区在线视频 | 成人在线观看免费 | 国内自拍偷拍 | 蜜桃av一区二区三区 | 欧美二区三区 | 欧美日韩精品中文字幕 | 久草热视频 | 精品国产一区二区三区av片 | 中文字幕动漫成人 | 亚洲国产欧美一区二区三区久久 | 欧美日韩中文字幕 | 久久久久一区二区三区 | 一区二区精品 | 国产精品久久久久久久久久尿 | 国产99久久久国产精品 | 国产精品国产成人国产三级 | 男人天堂999 | 国产成人精品一区二区三区视频 | 久久99精品久久久久久国产越南 | 久久精品无码一区二区三区 | 国产日韩欧美电影 | 国产一区二区三区久久久久久久久 | 免费在线h视频 | 欧洲视频一区二区 | 国产精品久久国产精品 | 阿v视频在线观看 | 日本超碰 | 一级毛片视频在线 | 日本在线一二 | 日韩成人免费av | 欧美在线一区二区三区四区 | 精品少妇一区二区三区在线播放 | 福利片在线观看 | 亚洲精品乱码 | 日批免费在线观看 | 九九综合 | 国产中文字幕亚洲 |