Server-sent-events 造成的 HTTP 缓存坑

前端开发者们在设计基于 HTTP 协议的应用时,会经常遇到浏览器缓存问题。为了提高应用的性能,浏览器通常会缓存一些静态文件(如 JS、CSS 文件等),从而避免在后续访问中重复下载。然而,当我们使用 Server-sent-events 技术时,HTTP 缓存就会带来一些问题。

Server-sent-events

Server-sent-events(SSE),也称为 EventSource,是一种向客户端推送事件的技术。通过 SSE,服务器可以实时地向客户端发送事件和数据,而客户端则可以通过 JavaScript 进行监听和处理。SSE 基于 HTTP,使用的是长连接,因此可以实现实时的双向通信。

下面是一个 SSE 的基本示例:

----- ----------- - --- --------------------

--------------------------------------- --------------- -
  -------------------- ----------- ------------
---

------------------------------------- --------------- -
  ----------------------- -------
---

在这个示例中,我们通过 EventSource 对象向 /sse 路径建立 SSE 连接。当服务器发送消息时,message 事件会被触发,此时我们可以通过 event.data 获取到服务器发送的数据。如果出现错误,error 事件会被触发。

HTTP 缓存

HTTP 缓存是指浏览器缓存一些静态资源,避免重复下载。HTTP 缓存的实现方式有两种:基于时间的缓存和基于内容的缓存。

基于时间的缓存是指浏览器在第一次请求某个资源时,会将Cache-ControlExpires 等头部信息保存下来,然后在后续访问时,仅当缓存过期或者不存在时才会重新下载。基于内容的缓存是指浏览器比较请求头部信息和响应头部信息中的 ETagLast-Modified 等信息,如果一致则表示资源未发生变化,可以直接从缓存中读取。

Server-sent-events 和 HTTP 缓存

SSE 本身并不会影响 HTTP 缓存,但是当使用了 HTTP 缓存时,SSE 发送的事件会被缓存,从而导致客户端无法实时接收到服务器的推送。

为了更好地理解这个问题,我们可以假设有一个 SSE 服务器不停地发送数字,客户端进行监听和显示。下面是使用基于时间的缓存的示例:

--------- -----
----- ----------
------
  ----- ----------------
  ---------- ------------
  --------
    -------- ------------------- -
      ----- ----------- - --- --------------------
    
      --------------------------------------- --------------- -
        ------------------------------------------- - -----------
      ---
    
      ------------------------------------- --------------- -
        ----------------------- -------
      ---
    -
  ---------
-------
----- -----------------------------
  ------- ---------
  ---- ----------------
-------
-------

在这个示例中,我们使用 onload 事件调用 createEventSource() 函数建立 SSE 连接,当服务器发送消息时,我们使用 JavaScript 更新页面中的 data 元素。

现在我们来看一下 SSE 服务器代码:

--- ------- - --

---------------------- -
  ------- -- --
  ----------------------- ---------

  ------------------
-- ------

这个服务器会不断地向客户端发送数字,每秒钟发一次。

假设我们现在在浏览器中打开这个页面,并启用基于时间的缓存。这时,我们在第一次打开页面时会发现数字正常地被更新。然而,如果你打开 DevTools,查看 Network 面板,你会发现 /sse 路径并没有被重新请求,也就是说,SSE 事件被缓存了。

现在,我们关闭浏览器,1 分钟后再打开页面,你会发现页面上的数字依然是上一次的数字。这是因为,虽然连接是实时的,但是由于使用了基于时间的缓存,浏览器仍然会从缓存中读取过期的数据。

解决方案

为了解决这个问题,我们需要做以下两点改进:

  1. 禁用 SSE 事件的缓存。
  2. 在 SSE 事件中添加一个唯一的标识符,来避免客户端接受到的事件被缓存。

为了禁用 SSE 事件的缓存,我们可以在服务器的响应头部信息中添加 Cache-Control: no-cachePragma: no-cache 等信息。这样浏览器就不会缓存这些事件了。在 Express 框架中,这个可以通过以下代码实现:

---------
  ---------------- -----------
  --------- -----------
  --------------- --------------------
  ------------- -------------
  -------------------- ----
---

为了在 SSE 事件中添加一个唯一的标识符,我们可以使用 Date.now() 方法来生成一个随机数,然后把这个随机数放在 SSE 事件的数据中。这个随机数可以作为事件的标识符使用,避免客户端接受到的事件被缓存。

下面是改进后的 SSE 服务器代码:

--- ------- - --

---------------------- -
  ------- -- --
  ----------------------- ---------

  ----------
    ----- ------ ----------------- --------------
  ---
-- ------

在这个示例中,我们在 SSE 事件的数据中添加了一个随机数作为标识符。这样,浏览器就无法把这些事件缓存下来了。

结论

在使用 SSE 技术时,HTTP 缓存会给我们带来一些问题。通过禁用 SSE 事件的缓存,我们可以解决这个问题,并且通过添加唯一的标识符,我们可以避免客户端接受到的事件被缓存。在实际项目中,我们需要根据具体的应用场景和需求,决定是否使用 HTTP 缓存以及如何优化 SSE 技术的使用。

参考链接

来源:JavaScript中文网 ,转载请注明来源 本文地址:https://www.javascriptcn.com/post/671f4d452e7021665efce431