在现代 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 方法向客户端发送自定义事件及其数据,例如:
sse.write({id: 1, event: 'my_event', data: {message: 'Hello, world!'}})
上述代码就向客户端推送了一个名为 'my_event' 的自定义事件,其数据为 {"message": "Hello, world!"}。在浏览器端通过监听相应事件,在收到消息时就可以进行相应的处理了。例如:
var source = new EventSource('/my_controller/stream'); source.addEventListener('my_event', function(event) { var data = JSON.parse(event.data); alert(data.message); });
上述代码中,我们通过新建一个 EventSource 对象指定事件源为 /my_controller/stream 路径约定的 SSE 推送入口。然后我们通过 addEventListener 方法监听名为 'my_event' 的自定义事件,一旦后端向客户端推送了该事件,浏览器就会在相应方法内进行相应的处理,例如给出一个弹窗显示消息内容。
权限控制、错误处理等常见问题
在实际开发中,我们还需要考虑一些常见问题,例如权限控制、错误处理等。下面是一些可能用到的技巧。
认证与授权
在某些场景下,我们可能需要对 SSE 推送进行认证与授权。例如,我们可能需要只向登陆用户发送推送消息,或者需要限制某些用户只能接收特定类型的消息。针对这种情况,我们可以在 SSE 推送入口处增加相应的认证与授权逻辑,例如:
-- -------------------- ---- ------- ----- ------------ - --------------------- ------- ---------------------- ------------- ------------------- --- ------ -- ------------------- - ---------------- --- -- -------------------- ---- - --------------- --- -- ----- - -------------- ------ --------- ------------------------------ - ------------------ - --- --- ---------------------- --- ------ ------- ------------ ----------- ------- ------ --------------------- --- --- -------------------- - --- ---- - --- --- --- --------------------------- - --- ---- - --- --- ---
上述代码中,我们在 stream
方法前加入了一个 before_action
来认证用户是否已登陆。然后根据不同用户身份确定 SSE 推送逻辑。比如管理员可以订阅所有类型消息的事件,而其他用户只能订阅特定类型消息的事件。我们创建了两个 SSE 推送方法,分别为 subscribe_all_events
和 subscribe_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 源代码):
-- -------------------- ---- ------- ----- --------------- - --------------------- ------- ---------------------- ------------- ------------------- --- ----- ------ - ---------------------- ------ --- --- ------ ------------------- ------------- -------- ----------------- ----------- ---------- --- --- ------ -------------------------------- - ------------------- --- - ------------------------ ------ ---- ------ ----------- ----- - -------------- ----------------- ----------------------- -- ---- ---------- -- --------- ---- -------------------------- --- -------------- ------ ----------- --- --- ------ ------- ---------- ---- ----------- ---------------- ------ ---------- --------------------- --- ---
客户端的代码如下:
-- -------------------- ---- ------- --------- ----- ------ ------ ----- ---------------- ----------------------- --- ------------------- -------------- ------ ------ ------------------------ -------- -- --- ---------------------- -------------- ------------------------ -------- -- --- -------------- -- ------- ------ -- -- --------------- -- ------ --- --------------------- --- --- ------- ----- ----- -------------------------- ------- ------- -- -- ---- -- --- ------- ----- ---- -------------------------- -- - --- ------- ---- ---- --------------------- -- -- --- -- --- -------- ----------- ------- ----- -- --- -- --- --------- ---------- --------- -- --- -------------- --------- -- --- ---------- -------- -- -- --- -- ---- --- ----------- -- ----------- -- ------ -- ------- -------------------- -- ----- --- ------------ -- ---- ------------------------- -------- -- --- -- ----- -------- --- ------ - --- ----------------------------- ----------------------------------- --------------- - --- ---- - ----------------------- ------------------ --- -------- - ----------------------------- ------------------ - ----- - -------------------- - ------ - - ------------ - - -- - --------------- - ---- ------------------------------------------------------- --- --------- ------- -------
运行测试:
$ rails s $ redis-server $ redis-cli publish chat '{"sender_id": 1, "sender": {"username": "alice", "id": 1}, "content": "Hello, world!", "created_at": "2021-10-01T07:20:50.335Z", "updated_at": "2021-10-01T07:20:50.335Z"}'
访问 http://localhost:3000/chats 可以看到聊天系统的页面,可以发送消息并实时接收发送记录。
总结
本文介绍了如何在 Ruby on Rails 中使用 Sever-Sent Events(SSE)实现实时数据推送,并针对认证与授权、错误处理等常见问题进行了相应解释。SSE 技术与传统的 Ajax 轮询相比,其优点在于可以让服务端向客户端主动推送消息,因此可以更加及时地响应用户操作。常见的应用场景包括聊天系统、实时数据监控等。如果您正在开发这类应用,可以尝试使用 SSE 来实现实时数据推送功能。
来源:JavaScript中文网 ,转载请注明来源 https://www.javascriptcn.com/post/64ddb41bf6b2d6eab38edcdb