如何在 Ruby on Rails 中使用 Server-Sent Events 实现实时数据推送

阅读时长 13 分钟读完

在现代 web 应用中,实时数据推送已经成为了非常重要的功能之一。而 Server-Sent Events(简称 SSE)是一种能够实现服务器与浏览器之间持久连接的技术,其原理是利用 HTTP 长连接在服务器与客户端之间传递事件。在 Ruby on Rails 中,我们可以借助 Action Controller 中的 ActionController::Live 模块轻松地实现 SSE。

SSE 的优点

相比起 WebSocket 技术,SSE 技术更简单易用,因为它只需要使用普通的 HTTP 协议进行通信,不需要额外的协议握手操作。同时,SSE 也支持自定义事件及数据,比较适合处理简单的实时数据推送需求。

Rails 中的 SSE 实现

要在 Rails 中使用 SSE,我们需要先在控制器中引入 ActionController::Live 模块,并建立一个 respond_to 块:

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

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

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

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

---

上述代码中的 stream 方法就是我们的 SSE 推送入口。我们通过设置 HTTP 头部的 Content-Type 为 text/event-stream 使得浏览器对本次请求的响应以 SSE 的方式进行处理。然后,我们创建了一个 SSE 对象 sse,并将 response.stream 传给它,以便后续可以使用它来推送 SSE 事件。最后我们调用了 sse.close,这将通知浏览器本次 SSE 推送已经完成,浏览器也将释放连接。

在 SSE 推送逻辑中,我们可以使用 sse.write 方法向客户端发送自定义事件及其数据,例如:

上述代码就向客户端推送了一个名为 'my_event' 的自定义事件,其数据为 {"message": "Hello, world!"}。在浏览器端通过监听相应事件,在收到消息时就可以进行相应的处理了。例如:

上述代码中,我们通过新建一个 EventSource 对象指定事件源为 /my_controller/stream 路径约定的 SSE 推送入口。然后我们通过 addEventListener 方法监听名为 'my_event' 的自定义事件,一旦后端向客户端推送了该事件,浏览器就会在相应方法内进行相应的处理,例如给出一个弹窗显示消息内容。

权限控制、错误处理等常见问题

在实际开发中,我们还需要考虑一些常见问题,例如权限控制、错误处理等。下面是一些可能用到的技巧。

认证与授权

在某些场景下,我们可能需要对 SSE 推送进行认证与授权。例如,我们可能需要只向登陆用户发送推送消息,或者需要限制某些用户只能接收特定类型的消息。针对这种情况,我们可以在 SSE 推送入口处增加相应的认证与授权逻辑,例如:

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

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

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

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

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

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

---

上述代码中,我们在 stream 方法前加入了一个 before_action 来认证用户是否已登陆。然后根据不同用户身份确定 SSE 推送逻辑。比如管理员可以订阅所有类型消息的事件,而其他用户只能订阅特定类型消息的事件。我们创建了两个 SSE 推送方法,分别为 subscribe_all_eventssubscribe_event,代码中并未给出具体实现,重点是展示了如何针对不同用户身份订阅不同的 SSE 事件。如果用户无法订阅指定类型消息时,我们可以在 rescue 块中进行相应的处理。

错误处理

在 SSE 推送过程中,服务端、客户端都可能发生意外错误,我们需要对这些错误进行相应处理。以下是一些常见错误的处理方式。

请求过程中出现的错误

如果服务端在 SSE 推送时发生错误,例如因为客户端断开连接导致 IOError,我们需要在 rescue 块中进行相应的错误处理。例如,如果我们需要使客户端退出 SSE 推送时触发跳转到一个特定的页面,我们可以这样写:

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

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

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

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

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

---

上述代码中,我们在 rescue 块中增加了跳转逻辑,将客户端导向 /error 页面。 turbolinks: false 是为了避免 Turbolinks 的影响,确保可以正常跳转。

客户端出现的错误

如果客户端在 SSE 接收过程中出现错误,例如前端代码出错,我们可能需要对这些错误进行相应的处理。由于 SSE 推送是以持久连接的方式进行的,一旦发生错误可能会使推送过程中断,我们可以通过定时的 ping 来确认连接是否正常并使用 retry: 字段指定重新连接的时间间隔。例如:

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

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

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

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

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

---

上述代码中,我们在发送数据之前增加了一个 try-catch 块来捕获可能发生的 IOError 错误,一旦捕获到错误就关闭 SSE 推送。由于客户端出错的情况较为复杂,我们并不知道客户端什么时候出现了错误,因此我们可以在一定时间间隔后尝试重新连接服务器。如果我们使用的是 EventSource API,我们可以通过 eventSource.close() 显式关闭 SSE 连接,以便重新连接服务器。另外,如果客户端与服务器之间的连接出现了大量不可恢复的错误,最好建立一个定时任务将这些错误进行记录并给出相应提示。例如,前端代码可能出现了一些不可捕获的 JavaScript 错误,我们可以在后端监控相关的错误日志,一旦出现错误就通知用户进行相应的提示。

示例代码

示例代码是一个简单的聊天系统,可以让用户进行发送消息,并通过 SSE 进行广播。以下是服务器端的代码(参见 GitHub 源代码):

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

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

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

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

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

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

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

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

---

客户端的代码如下:

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

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

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

  ----

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

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

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

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

运行测试:

访问 http://localhost:3000/chats 可以看到聊天系统的页面,可以发送消息并实时接收发送记录。

总结

本文介绍了如何在 Ruby on Rails 中使用 Sever-Sent Events(SSE)实现实时数据推送,并针对认证与授权、错误处理等常见问题进行了相应解释。SSE 技术与传统的 Ajax 轮询相比,其优点在于可以让服务端向客户端主动推送消息,因此可以更加及时地响应用户操作。常见的应用场景包括聊天系统、实时数据监控等。如果您正在开发这类应用,可以尝试使用 SSE 来实现实时数据推送功能。

来源:JavaScript中文网 ,转载请注明来源 https://www.javascriptcn.com/post/64ddb41bf6b2d6eab38edcdb

纠错
反馈