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