您好,登錄后才能下訂單哦!
這篇文章主要介紹“HTTP中ETag語法及使用方法是什么”的相關(guān)知識,小編通過實(shí)際案例向大家展示操作過程,操作方法簡單快捷,實(shí)用性強(qiáng),希望這篇“HTTP中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)。
ETag 是一個(gè)不透明的標(biāo)識符,由 Web 服務(wù)器根據(jù) URL 上的資源的特定版本而指定。如果 URL 上的資源內(nèi)容改變,一個(gè)新的不一樣的 ETag 就會(huì)被生成。ETag 可以看成是資源的指紋,它們能夠被快速地比較,以確定兩個(gè)版本的資源是否相同。
需要注意的是 ETag 的比較只對同一個(gè) URL 有意義 —— 不同 URL 上資源的 ETag 值可能相同也可能不同。
ETag: W/"<etag_value>" 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ù)字的哈希值。
在大多數(shù)場景下,當(dāng)一個(gè) URL 被請求,Web 服務(wù)器會(huì)返回資源和其相應(yīng)的 ETag 值,它會(huì)被放置在 HTTP 響應(yīng)頭的 ETag 字段中:
HTTP/1.1 200 OK Content-Length: 44 Cache-Control: max-age=10 Content-Type: application/javascript; charset=utf-8 ETag: W/"2c-1799c10ab70"
然后,客戶端可以決定是否緩存這個(gè)資源和它的 ETag。以后,如果客戶端想再次請求相同的 URL,將會(huì)發(fā)送一個(gè)包含已保存的 ETag 和 If-None-Match 字段的請求。
GET /index.js HTTP/1.1 Host: localhost:3000 Connection: keep-alive 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)碼告訴客戶端,它的緩存版本是最新的,可以直接使用它。
HTTP/1.1 304 Not Modified Cache-Control: max-age=10 ETag: W/"2c-1799c10ab70" Connection: keep-alive
了解完 ETag 相關(guān)知識后,基于 koa
、koa-conditional-get
、koa-etag
和 koa-static
這些庫來介紹一下,在實(shí)際項(xiàng)目中如何利用 ETag
響應(yīng)頭和 If-None-Match
請求頭實(shí)現(xiàn)資源的緩存控制。
// server.js const Koa = require("koa"); const path = require("path"); const serve = require("koa-static"); const etag = require("koa-etag"); const conditional = require("koa-conditional-get"); const app = new Koa(); app.use(conditional()); // 使用條件請求中間件 app.use(etag()); // 使用etag中間件 app.use( // 使用靜態(tài)資源中間件 serve(path.join(__dirname, "/public"), { maxage: 10 * 1000, // 設(shè)置緩存存儲(chǔ)的最大周期,單位為秒 }) ); app.listen(3000, () => { console.log("app starting at port 3000"); });
在以上代碼中,使用了 koa-static 中間件來處理靜態(tài)資源,這些資源被保存在 public
目錄下。在該目錄下,創(chuàng)建了 index.html 和 index.js 兩個(gè)資源文件,文件中的內(nèi)容分別如下所示:
<!DOCTYPE html> <html lang="zh-cn"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>ETag 使用示例</title> <script src="/index.js"></script> </head> <body> <h4>ETag 使用示例</h4> </body> </html>
console.log("大家好");
在啟動(dòng)完服務(wù)器之后,打開 Chrome 開發(fā)者工具并切換到 Network 標(biāo)簽欄,然后在瀏覽器地址欄輸入 http://localhost:3000/ 地址,接著多次訪問該地址(地址欄多次回車)。下圖是多次訪問的結(jié)果:
下面以 index.js 為例,來分析上圖中與之對應(yīng)的 HTTP 報(bào)文。對于 index.html 文件,感興趣的小伙伴可以自行分析一下。接下來先來分析首次請求 index.js 文件的報(bào)文:
GET /index.js HTTP/1.1 Host: localhost:3000 Connection: keep-alive Pragma: no-cache Cache-Control: no-cache ...
HTTP/1.1 200 OK Content-Length: 44 Cache-Control: max-age=10 ETag: W/"2c-1799c10ab70" ...
在使用了 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
的緩存管理更有效,安全一些。
GET /index.js HTTP/1.1 Host: localhost:3000 Connection: keep-alive Pragma: no-cache Cache-Control: no-cache ...
Request URL: http://localhost:3000/index.js Request Method: GET Status Code: 200 OK (from memory cache) Remote Address: [::1]:3000 Referrer Policy: strict-origin-when-cross-origin
Cache-Control: max-age=10 Connection: keep-alive Content-Length: 44 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)
。
GET /index.js HTTP/1.1 Host: localhost:3000 Connection: keep-alive If-None-Match: W/"2c-1799c10ab70" Referer: http://localhost:3000/ ...
因?yàn)?10s 之后,緩存已經(jīng)過期了,而且在 index.js
文件首次請求的響應(yīng)報(bào)文中也返回了 ETag
字段。所以此時(shí)瀏覽器會(huì)發(fā)起 If-None-Match
條件請求。這類請求可以用來驗(yàn)證緩存的有效性,省去不必要的控制手段。
HTTP/1.1 304 Not Modified Cache-Control: max-age=10 ETag: W/"2c-1799c10ab70" Connection: keep-alive ...
因?yàn)槲募膬?nèi)容未發(fā)生改變,所以 10s 后的響應(yīng)報(bào)文的狀態(tài)碼為 304 Not Modified。此外,響應(yīng)報(bào)文中也返回了 ETag
字段??吹竭@里,有一些小伙伴可能會(huì)有疑惑 —— ETag 到底是如何生成的?接下來揭開 koa-etag
中間件背后的秘密。
在前面的示例中,使用了 koa-etag 中間件來實(shí)現(xiàn)資源的緩存控制。其實(shí)該中間件的實(shí)現(xiàn)并不復(fù)雜,具體如下所示:
// https://github.com/koajs/etag/blob/master/index.js const calculate = require('etag'); // 省略部分代碼 module.exports = function etag (options) { return async function etag (ctx, next) { await next() const entity = await getResponseEntity(ctx) setEtag(ctx, entity, options) } }
由以上代碼可知,在 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。
// https://github.com/koajs/etag/blob/master/index.js function setEtag (ctx, entity, options) { if (!entity) return ctx.response.etag = calculate(entity, options) }
etag
這個(gè)庫對外提供了一個(gè) etag
函數(shù)來創(chuàng)建 ETag,該函數(shù)的簽名如下:
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):
function etag (entity, options) { if (entity == null) { throw new TypeError('argument entity is required') } // 支持fs.Stats對象 // isstats 函數(shù)的判斷規(guī)則:當(dāng)前對象是否包含ctime、mtime、ino和size這些屬性 var isStats = isstats(entity) var weak = options && typeof options.weak === 'boolean' ? options.weak : isStats // 參數(shù)校驗(yàn) if (!isStats && typeof entity !== 'string' && !Buffer.isBuffer(entity)) { throw new TypeError('argument entity must be string, Buffer, or fs.Stats') } // 生成ETag標(biāo)簽 var tag = isStats ? stattag(entity) // 處理fs.Stats對象 : entitytag(entity) return weak ? 'W/' + tag : tag }
在 etag
函數(shù)內(nèi)部會(huì)根據(jù) entity
的類型,執(zhí)行不同的生成邏輯。如果 entity
是 fs.Stats
對象,則會(huì)調(diào)用 stattag
函數(shù)來創(chuàng)建 ETag。
function stattag (stat) { // mtime:Modified Time,是在寫入文件時(shí)隨文件內(nèi)容的更改而更改,是指文件內(nèi)容最后一次被修改的時(shí)間。 var mtime = stat.mtime.getTime().toString(16) var size = stat.size.toString(16) return '"' + size + '-' + mtime + '"' }
而如果 entity
參數(shù)非 fs.Stats
對象,則會(huì)調(diào)用 entitytag
函數(shù)來生成 ETag。其中 entitytag
函數(shù)的具體實(shí)現(xiàn)如下:
function entitytag (entity) { if (entity.length === 0) { return '"0-2jmj7l5rSw0yVb/vlWAYkK/YBwk"' } // 計(jì)算實(shí)體對象的哈希值 var hash = crypto .createHash('sha1') .update(entity, 'utf8') .digest('base64') .substring(0, 27) // 計(jì)算實(shí)體對象的長度 var len = typeof entity === 'string' ? Buffer.byteLength(entity, 'utf8') : entity.length 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í)間戳的哈希值或簡單地使用版本號。
其實(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ā)生變化。
關(guān)于“HTTP中ETag語法及使用方法是什么”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識,可以關(guān)注億速云行業(yè)資訊頻道,小編每天都會(huì)為大家更新不同的知識點(diǎn)。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。