让我在2021年为你解释一下,同时提供更新的信息和代码链接。
这是一个相对直接简单的概念,但作为开发者,你应该真正了解它,因为它可能会给你带来麻烦!
什么是Etag?
根据
Wikipedia/Etag,Etag是一个HTTP头部。它可以在某些GET调用的DevTools中的“响应标头”部分中看到,如下面的截图所示。
![enter image description here](https://istack.dev59.com/H1qx4.webp)
在Express中,ETag可以以
W/
(弱,缺省)开头或不带(强),然后是
<LEN>-<VALUE>
,其中VALUE长度为27个字符,并且LEN是VALUE的十六进制长度。(
2021年6月源代码)
ETag的目的是什么?
啊,好问题。答案是:缓存!
(PS. 只缓存客户端和服务器之间的网络流量。也就是响应数据通过HTTP(S)发送到客户端的传输;而不是任何服务器到数据库的内部缓存等。)
如何缓存?
机制相对简单。
假设客户端(如Chrome浏览器)调用https://myserver.com/user/profile/get
终端点并获得当前用户所有配置文件数据的大型JSON响应(例如,30个字段的名称、电话、照片URL等)。除了向您的应用程序交付响应作为JSON对象之外,客户端还将在其自己的私有内部网络层中将此数据存储在客户端侧缓存中:{'https://myserver.com/users/profile/get': <this-json-response-object> }
。
现在,下一次(甚至几天和会话后)客户端即将调用相同终端点.../user/profile/get
时,它可以告诉服务器:“嘿,我在我的缓存中有这个<previous_json_from_the_cache>,如果你要发送的内容与此完全相同,请不要发送它。”
很酷,但这不是效率低下吗?
是的!
问题在于,如果客户端将整个JSON对象从缓存中发送到服务器请求中,这既是安全风险,也是相当低效的——同样的30个字段JSON对象可能会被发送两次以上。
这里发生的是,客户端(即Chrome浏览器)可以计算哈希值(例如MD5,既不可逆,又更短),并在第二个请求中说:“嘿,如果你要发送给我回来的JSON的MD5哈希值是这个<computed_hash>
,我已经有它了!所以不用再发送一次。”
现在,服务器将像以前一样计算响应(从数据库中提取所有数据)。但是,在仅在发送响应数据之前,它会在服务器端计算响应的哈希值,以查看是否与客户端所说的匹配。如果是,则发送304 HTTP状态响应代码,而不是200,这意味着“没有任何更改。”
好的!就是这个吗?
嗯,在上面的例子中,如果你仔细观察,哈希计算是在客户端和服务器端都发生的。这会使更改算法变得困难。所以,实际上,“响应的哈希值”只在第一次由服务器端计算,并将返回给客户端。
这个“当前响应”的计算哈希值会随着响应一起返回,它在响应的ETag
头中。
因此,每当客户端接收到响应时,它会在内部缓存中存储:{".../profile/get": [<ETag>, <JSON-Response-Data>]}
。
然后,在任何未来的请求中,客户端将向服务器发送此ETag
值(在某些标头中,如if-none-match
),以表示如果新调用的响应将具有与此相同的ETag
,则可以接收304。
因此,简要回顾一下:
ETag
值只是响应数据(正文)的不可逆、短且快速的哈希值。
- 服务器在响应中发送
ETag
头到客户端。
- 客户端在请求中发送
if-none-matched
头(其值是之前从服务器接收到的Etag
值)到服务器。
太好了!我该如何使用它?
默认情况下,它是在Express.js中进行的。所以,请坐享其成!
很少有情况需要您更改其设置。
什么情况下我不应该使用Etag?
啊!欢迎来到我的世界:D 这就是我来到这里并进行所有这些研究的方式。
Express uses etag package(只有一个文件,由同一批人管理)生成ETag值。在内部,etag
包使用sha1
加密body,而没有其他奇怪的东西,以保持最佳性能。(如果您想象一下,此函数将被频繁调用!服务器接收和处理的每个GET调用平均至少一次或两次。)
为了确定它是否应该执行304还是200,当客户端说“我已经在我的缓存中有这些值了”时,Express使用fresh package(再次只有一个文件,实际上只有一个返回布尔值的函数,由同一批人维护)。在内部,fresh
包读取请求头的if-none-matched
标记(reqHeaders ['if-none-match']
)并compares它与即将发送的响应的etag
(resHeaders ['etag']
)。
很棒,那么问题在哪里?
当您的架构和客户端与服务器之间的通信依赖于自定义标头时,问题就会出现!
例如,您想要在任何请求上更新auth或session token,并在某些请求的RESPONSE HEADER上在后台刷新并发送新令牌。
目前EXPRESS的ETag实现仅依赖于响应体,而不是响应头。即使是他们允许放置的自定义函数(doc,code),也只取响应体内容,而不是响应头。
因此,当响应(例如个人资料数据)未更改时,您的客户端可能会重用过时的身份验证令牌,并由于无效的身份验证/会话标记将用户踢出!
如何禁用它?
您可以执行app.set("etag", false);
以停止Express发送它。 根据this answer,您还可以/应该通过app.use(nocache())
使用nocache向客户端从服务器发送“嘿,客户端,永远不要自己缓存它!”头文件。
干杯!
PS. 最后说明:
- 如果您考虑一下,ETags对于资产非常有价值(当响应数据大小达到100KB或更多时),但对于普通API端点数据则不是。 因此,为小型响应端点禁用它可能不是一个坏主意-事实上,这可能不值得支付开销。