让我在 2021 年用更新的信息和代码链接来解释它。
这是一个相对直接和简单(不是火箭科学)的概念,但同时也是一个非常棘手的事情,作为开发人员,在它咬你之前你应该真正知道!
什么是埃塔格?
因此,Etag(根据Wikipedia/Etag)是一个 HTTP 标头。
它可以在 DevTools中一些GET调用的“响应标头”部分看到,如下面的屏幕截图。
在 Express 中,它可以以W/
(weak, default ) 或非 (strong) 开头,然后<LEN>-<VALUE>
是 ,其中 VALUE 是 27 个字符长,LEN 是 VALUE 的十六进制长度。(2021年6月源码)
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
,它可以告诉服务器“嘿,我的缓存中有这个 <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
所以,回顾一下:
ETag
value 并不疯狂,而是响应数据(主体)的不可逆、短且快速的散列值。
ETag
服务器在响应中向客户端发送标头。
- 客户端在请求中向服务器发送
if-none-matched
标头(其值是先前Etag
从服务器接收到的值)。
伟大的!我该如何使用它?
默认情况下,它发生在 Express.js 中。所以,坐下来享受吧!
您不太可能需要弄乱它的设置。
我什么时候不应该使用 Etag?
啊! 欢迎来到我的生活。:D 这就是我来到这里并进行所有这些研究的方式。
Express 包使用etag包(它只是一个文件,由同一个人管理)来生成 ETag 值。在内部,该etag
包使用 主体sha1
加密,并没有什么疯狂的,以保持最佳性能。(如果你想象,这个函数会被调用很多次!服务器接收和处理的任何GET 调用平均至少一次或两次。)
为了决定它应该执行 304 还是 200,当客户说“我的缓存中已经有了这些值”时,Express 使用新的包(同样只有一个文件,实际上只有一个返回布尔值的函数,由同一个人维护)。在内部,fresh
包读取if-none-matched
请求标头 ( reqHeaders['if-none-match']
)的标记并将其与将要发送etag
的响应 ( ) 的标记进行比较。resHeaders['etag']
酷,那有什么问题吗?
当您的架构以及客户端和服务器之间的通信依赖于自定义标头时,就会出现问题!
例如,您想在任何请求上更新身份验证或会话令牌,并在后台刷新它并发送一个新的,作为某些请求的响应标头。
当前 Etag 的 EXPRESS 实现,仅依赖于响应体,而不依赖于响应头。 甚至,他们允许放置的自定义功能(doc,code)仅采用正文内容,而不是响应标头。
因此,发生的情况是,当响应(例如配置文件数据)未更改时,您的客户端可能会重用过时的 auth-token 并由于无效的 auth/session 标签而将用户踢出!
我怎样才能禁用它?
您可以这样app.set("etag", false);
做 Express 停止发送它。根据这个答案,您可以/应该也使用nocache viaapp.use(nocache())
来发送“嘿客户,不要打扰自己缓存它!” 从服务器到客户端的标头。
干杯!
PS。最后说明:
- 如果你仔细想想,ETags 对于资产(当响应数据的大小为 100KB 或更大时)非常有价值,但对于常见的 API Endpoints 数据则不然。因此,为您的小响应端点禁用它可能不是一个坏主意——实际上,不支付开销可能值得。